mirror of
https://github.com/lddsb/drone-dingtalk-message.git
synced 2026-06-16 14:50:42 +08:00
Compare commits
62 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0d2783aeb9 | |||
| 07310794cb | |||
| a165a28c60 | |||
| 43222b966a | |||
| 530b471503 | |||
| 3a0957f479 | |||
| c9a57df515 | |||
| d0a4fc64e4 | |||
| 526b8eec46 | |||
| 8729ae05b4 | |||
| cec9f81f52 | |||
| 428577470d | |||
| 7c8a85bcc4 | |||
| ecf4f89e0a | |||
| e7ef2de488 | |||
| acd708ada3 | |||
| 5bcf803346 | |||
| 1b219624cd | |||
| d409acce48 | |||
| 2e94325c0e | |||
| 1622a2beaa | |||
| d2ab02e29c | |||
| d0284b2434 | |||
| c8bc5d889d | |||
| d4dc439671 | |||
| 8d0434b308 | |||
| 8aafd04b82 | |||
| a651a73cbd | |||
| 7c68447d1d | |||
| 183999881b | |||
| 4c52115e2a | |||
| fb601a0d6c | |||
| 8bd745becf | |||
| e9aea90aac | |||
| c77b865a43 | |||
| fbfbec3bde | |||
| 30c2324f4e | |||
| 11d34af11a | |||
| f9aff987bb | |||
| 8164de1292 | |||
| 9cb8a9b160 | |||
| 9eed74739b | |||
| 664c2e90c2 | |||
| 8d080c3b0c | |||
| 0734cc50ff | |||
| 00fb440336 | |||
| 580a43e7a1 | |||
| 5ed5fe4bdc | |||
| 31767312c3 | |||
| 88dcf2d231 | |||
| 38be4d2982 | |||
| 0529a41a36 | |||
| 45450d3115 | |||
| 3beae7de72 | |||
| 6615f9033d | |||
| d8514a85d7 | |||
| dc155b39c7 | |||
| 6722997241 | |||
| c100d9e56f | |||
| 9dad7f2a03 | |||
| 20aaaf3454 | |||
| 74c317b189 |
+75
@@ -0,0 +1,75 @@
|
|||||||
|
---
|
||||||
|
kind: pipeline
|
||||||
|
name: testing
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: vet
|
||||||
|
pull: always
|
||||||
|
image: golang
|
||||||
|
commands:
|
||||||
|
- make vet
|
||||||
|
environment:
|
||||||
|
GO111MODULE: "on"
|
||||||
|
volumes:
|
||||||
|
- name: gopath
|
||||||
|
path: /go
|
||||||
|
|
||||||
|
- name: test
|
||||||
|
pull: always
|
||||||
|
image: golang
|
||||||
|
commands:
|
||||||
|
- make test
|
||||||
|
- make coverage
|
||||||
|
environment:
|
||||||
|
GO111MODULE: "on"
|
||||||
|
volumes:
|
||||||
|
- name: gopath
|
||||||
|
path: /go
|
||||||
|
|
||||||
|
- name: codecov
|
||||||
|
pull: always
|
||||||
|
image: plugins/codecov
|
||||||
|
settings:
|
||||||
|
token:
|
||||||
|
from_secret: codecov_token
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- name: gopath
|
||||||
|
temp: {}
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
event:
|
||||||
|
- pull_request
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: pipeline
|
||||||
|
name: dryrun
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: build
|
||||||
|
pull: always
|
||||||
|
image: golang
|
||||||
|
commands:
|
||||||
|
- go build -a -o drone-dingtalk-message .
|
||||||
|
environment:
|
||||||
|
CGO_ENABLED: 0
|
||||||
|
GO111MODULE: "on"
|
||||||
|
|
||||||
|
- name: dryrun
|
||||||
|
pull: always
|
||||||
|
image: plugins/docker
|
||||||
|
settings:
|
||||||
|
cache_from: lddsb/drone-dingtalk-message
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
dry_run: true
|
||||||
|
repo: lddsb/drone-dingtalk-message
|
||||||
|
tags:
|
||||||
|
- latest
|
||||||
|
- 1.0.0
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
event:
|
||||||
|
- pull_request
|
||||||
|
|
||||||
|
depends_on:
|
||||||
|
- testing
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
[*.y*ml]
|
||||||
|
indent_size = 2
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: gomod
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: daily
|
||||||
|
time: "10:00"
|
||||||
|
open-pull-requests-limit: 10
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
name: Publish to DockerHub and Github Package
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
dockerhub:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Repo metadata
|
||||||
|
id: repo
|
||||||
|
uses: actions/github-script@v3
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const repo = await github.repos.get(context.repo)
|
||||||
|
return repo.data
|
||||||
|
- name: Prepare
|
||||||
|
id: prep
|
||||||
|
run: |
|
||||||
|
DOCKER_IMAGE=lddsb/drone-dingtalk-message
|
||||||
|
GITHUB_IMAGE=ghcr.io/lddsb/drone-dingtalk-message
|
||||||
|
VERSION=${GITHUB_REF#refs/tags/v}
|
||||||
|
TAGS="${DOCKER_IMAGE}:${VERSION}"
|
||||||
|
if [[ $VERSION =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
|
||||||
|
MINOR=${VERSION%.*}
|
||||||
|
MAJOR=${MINOR%.*}
|
||||||
|
TAGS="$TAGS,${DOCKER_IMAGE}:${MINOR},${DOCKER_IMAGE}:${MAJOR},${DOCKER_IMAGE}:latest"
|
||||||
|
TAGS="$TAGS,${GITHUB_IMAGE}:${VERSION},${GITHUB_IMAGE}:${MINOR},${GITHUB_IMAGE}:${MAJOR},${GITHUB_IMAGE}:latest"
|
||||||
|
fi
|
||||||
|
echo ::set-output name=tags::${TAGS}
|
||||||
|
- name: Setup Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Login to GitHub Container Register
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.CR_PAT }}
|
||||||
|
- name: Build and push
|
||||||
|
id: docker_build
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./Dockerfile
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.prep.outputs.tags }}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
name: Publish release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
name: publish release, upload asset
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: stable
|
||||||
|
- name: Run GoReleaser
|
||||||
|
uses: goreleaser/goreleaser-action@v5
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
args: release --clean
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.CR_PAT }}
|
||||||
+4
-1
@@ -1,4 +1,7 @@
|
|||||||
dive.log
|
dive.log
|
||||||
drone-dingtalk-message
|
drone-dingtalk-message
|
||||||
.idea
|
.idea
|
||||||
vendor
|
vendor
|
||||||
|
coverage.txt
|
||||||
|
coverage.out
|
||||||
|
env.list
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
env:
|
||||||
|
- GO111MODULE=on
|
||||||
|
before:
|
||||||
|
hooks:
|
||||||
|
- go mod download
|
||||||
|
builds:
|
||||||
|
- env:
|
||||||
|
- CGO_ENABLED=0
|
||||||
|
goos:
|
||||||
|
- linux
|
||||||
|
- darwin
|
||||||
|
- windows
|
||||||
|
goarch:
|
||||||
|
- 386
|
||||||
|
- amd64
|
||||||
|
- arm
|
||||||
|
- arm64
|
||||||
|
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||||
|
binary: dingtalk-message
|
||||||
|
flags:
|
||||||
|
- -trimpath
|
||||||
|
ldflags:
|
||||||
|
- -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{ .CommitDate }} -X main.builtBy=goreleaser
|
||||||
|
checksum:
|
||||||
|
name_template: '{{ .ProjectName }}_checksums.txt'
|
||||||
|
changelog:
|
||||||
|
sort: asc
|
||||||
|
filters:
|
||||||
|
exclude:
|
||||||
|
- '^docs:'
|
||||||
|
- '^test:'
|
||||||
|
- Merge pull request
|
||||||
|
- Merge branch
|
||||||
|
- go mod tidy
|
||||||
|
archives:
|
||||||
|
- name_template: '{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
|
||||||
|
replacements:
|
||||||
|
darwin: Darwin
|
||||||
|
linux: Linux
|
||||||
|
windows: Windows
|
||||||
|
386: i386
|
||||||
|
amd64: x86_64
|
||||||
|
format_overrides:
|
||||||
|
- goos: windows
|
||||||
|
format: zip
|
||||||
|
files:
|
||||||
|
- README.md
|
||||||
|
- LICENSE
|
||||||
|
- tpls/*
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [1.2.5] - 2020-12-16
|
||||||
|
### Added:
|
||||||
|
* The TPL can use environment variables.
|
||||||
|
* Debug mode.
|
||||||
|
* Use GitHub Actions for automation.
|
||||||
|
|
||||||
|
## [1.2.4] - 2020-04-28
|
||||||
|
### Fixed:
|
||||||
|
* kubernetes runner missing env, [details](https://docs.drone.io/runner/kubernetes/overview)
|
||||||
|
|
||||||
|
## [1.2.3] - 2020-01-20
|
||||||
|
### Fixed:
|
||||||
|
* CDN url of default image.
|
||||||
|
|
||||||
|
## [1.2.2] - 2019-12-07
|
||||||
|
### Added:
|
||||||
|
* Support DingTalk `sign secret`, please see the [README](README.md) for instructions.
|
||||||
|
|
||||||
|
## [1.2.1] - 2019-11-07
|
||||||
|
### Added:
|
||||||
|
* Support customize message tips title by `tips_title` option.
|
||||||
|
|
||||||
|
## [1.2.0] - 2019-09-24
|
||||||
|
### Added:
|
||||||
|
* Support custom TPL.
|
||||||
|
|
||||||
|
## [1.1.4] - 2020-04-28
|
||||||
|
### Fixed:
|
||||||
|
* kubernetes runner missing env, [details](https://docs.drone.io/runner/kubernetes/overview)
|
||||||
|
|
||||||
|
## [1.1.3] - 2020-01-20
|
||||||
|
### Fixed:
|
||||||
|
* CDN url of default image.
|
||||||
|
|
||||||
|
## [1.1.2] - 2019-11-07
|
||||||
|
### Added:
|
||||||
|
* Support customize message tips title by `tips_title` option.
|
||||||
|
|
||||||
|
## [1.1.1] - 2019-09-24
|
||||||
|
### Added:
|
||||||
|
* Support full token url as `token`.
|
||||||
|
|
||||||
|
## [1.1.0] - 2019-03-09
|
||||||
|
### Added:
|
||||||
|
* Package management tools migrate from dep to go mod, 1.0.x version stopped supporting new features.
|
||||||
|
|
||||||
|
## [1.0.2] - 2020-01-20
|
||||||
|
### Fixed:
|
||||||
|
* CDN url of default image.
|
||||||
|
|
||||||
|
## [1.0.1] - 2019-03-09
|
||||||
|
### Added:
|
||||||
|
* Auto publish image to DockerHub.
|
||||||
|
|
||||||
|
[Unreleased]: https://github.com/lddsb/drone-dingtalk-message/compare/v1.2.5...HEAD
|
||||||
|
[1.2.5]: https://github.com/lddsb/drone-dingtalk-message/compare/v1.2.4...v1.2.5
|
||||||
|
[1.2.4]: https://github.com/lddsb/drone-dingtalk-message/compare/v1.2.3...v1.2.4
|
||||||
|
[1.2.3]: https://github.com/lddsb/drone-dingtalk-message/compare/v1.2.2...v1.2.3
|
||||||
|
[1.2.2]: https://github.com/lddsb/drone-dingtalk-message/compare/v1.2.1...v1.2.2
|
||||||
|
[1.2.1]: https://github.com/lddsb/drone-dingtalk-message/compare/v1.2.0...v1.2.1
|
||||||
|
[1.2.0]: https://github.com/lddsb/drone-dingtalk-message/compare/v1.1.0...v1.2.0
|
||||||
|
[1.1.4]: https://github.com/lddsb/drone-dingtalk-message/compare/v1.1.3...v1.1.4
|
||||||
|
[1.1.3]: https://github.com/lddsb/drone-dingtalk-message/compare/v1.1.2...v1.1.3
|
||||||
|
[1.1.2]: https://github.com/lddsb/drone-dingtalk-message/compare/v1.1.1...v1.1.2
|
||||||
|
[1.1.1]: https://github.com/lddsb/drone-dingtalk-message/compare/v1.1.0...v1.1.1
|
||||||
|
[1.1.0]: https://github.com/lddsb/drone-dingtalk-message/compare/v1.0.0...v1.1.0
|
||||||
|
[1.0.2]: https://github.com/lddsb/drone-dingtalk-message/compare/v1.0.1...v1.0.2
|
||||||
|
[1.0.1]: https://github.com/lddsb/drone-dingtalk-message/compare/v1.0.0...v1.0.1
|
||||||
+9
-7
@@ -1,9 +1,11 @@
|
|||||||
|
FROM golang AS builder
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . .
|
||||||
|
RUN GO111MODULE=on CGO_ENABLED=0 GOOS=linux go build -a -o drone-dingtalk .
|
||||||
|
|
||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
|
RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/*
|
||||||
|
COPY --from=builder /app/drone-dingtalk /bin
|
||||||
|
COPY --from=builder /app/tpls /app/drone/dingtalk/message/tpls
|
||||||
|
|
||||||
RUN apk update && \
|
ENTRYPOINT ["/bin/drone-dingtalk"]
|
||||||
apk add \
|
|
||||||
ca-certificates && \
|
|
||||||
rm -rf /var/cache/apk/*
|
|
||||||
|
|
||||||
ADD drone-dingtalk-message /bin/
|
|
||||||
ENTRYPOINT ["/bin/drone-dingtalk-message"]
|
|
||||||
|
|||||||
Generated
-31
@@ -1,31 +0,0 @@
|
|||||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
|
||||||
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:1f4b55d7c57681ad91b7ce3ac7c8e7b139e6dd2f477c5f87e6a7749d298645dc"
|
|
||||||
name = "github.com/joho/godotenv"
|
|
||||||
packages = [
|
|
||||||
".",
|
|
||||||
"autoload",
|
|
||||||
]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "23d116af351c84513e1946b527c88823e476be13"
|
|
||||||
version = "v1.3.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:b24d38b282bacf9791408a080f606370efa3d364e4b5fd9ba0f7b87786d3b679"
|
|
||||||
name = "github.com/urfave/cli"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "cfb38830724cc34fedffe9a2a29fb54fa9169cd1"
|
|
||||||
version = "v1.20.0"
|
|
||||||
|
|
||||||
[solve-meta]
|
|
||||||
analyzer-name = "dep"
|
|
||||||
analyzer-version = 1
|
|
||||||
input-imports = [
|
|
||||||
"github.com/joho/godotenv/autoload",
|
|
||||||
"github.com/urfave/cli",
|
|
||||||
]
|
|
||||||
solver-name = "gps-cdcl"
|
|
||||||
solver-version = 1
|
|
||||||
-38
@@ -1,38 +0,0 @@
|
|||||||
# Gopkg.toml example
|
|
||||||
#
|
|
||||||
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
|
|
||||||
# for detailed Gopkg.toml documentation.
|
|
||||||
#
|
|
||||||
# required = ["github.com/user/thing/cmd/thing"]
|
|
||||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
|
||||||
#
|
|
||||||
# [[constraint]]
|
|
||||||
# name = "github.com/user/project"
|
|
||||||
# version = "1.0.0"
|
|
||||||
#
|
|
||||||
# [[constraint]]
|
|
||||||
# name = "github.com/user/project2"
|
|
||||||
# branch = "dev"
|
|
||||||
# source = "github.com/myfork/project2"
|
|
||||||
#
|
|
||||||
# [[override]]
|
|
||||||
# name = "github.com/x/y"
|
|
||||||
# version = "2.4.0"
|
|
||||||
#
|
|
||||||
# [prune]
|
|
||||||
# non-go = false
|
|
||||||
# go-tests = true
|
|
||||||
# unused-packages = true
|
|
||||||
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/joho/godotenv"
|
|
||||||
version = "1.3.0"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/urfave/cli"
|
|
||||||
version = "1.20.0"
|
|
||||||
|
|
||||||
[prune]
|
|
||||||
go-tests = true
|
|
||||||
unused-packages = true
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2016 Jeremy Saenz & Contributors
|
Copyright (c) 2019 Dee Luo
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
GO ?= go
|
||||||
|
PACKAGES ?= $(shell $(GO) list ./...)
|
||||||
|
|
||||||
|
vet:
|
||||||
|
$(GO) vet $(PACKAGES)
|
||||||
|
|
||||||
|
test:
|
||||||
|
@$(GO) test -v -cover -coverprofile coverage.txt $(PACKAGES) && echo "\n==>\033[32m Ok\033[m\n" || exit 1
|
||||||
|
|
||||||
|
coverage:
|
||||||
|
sed -i '/main.go/d' coverage.txt
|
||||||
@@ -1,29 +1,61 @@
|
|||||||
# Drone CI DingTalk Message Plugin
|
# Drone CI DingTalk Message Plugin
|
||||||
|
[](https://github.com/lddsb/drone-dingtalk-message/actions?query=workflow%3A%22Publish+to+DockerHub+and+Github+Package%22) [](https://goreportcard.com/report/github.com/lddsb/drone-dingtalk-message) [](https://codecov.io/gh/lddsb/drone-dingtalk-message) [](https://app.dependabot.com/accounts/lddsb/repos/159822771) [](LICENSE)
|
||||||
|
|
||||||
|
[中文说明](README_ZH.md)
|
||||||
|
|
||||||
|
<!-- toc -->
|
||||||
|
|
||||||
|
- [Drone CI Plugin Config](#drone-ci-plugin-config)
|
||||||
|
- [Plugin Parameter Reference](#plugin-parameter-reference)
|
||||||
|
- [TPL](#tpl)
|
||||||
|
- [Screen Shot](#screen-shot)
|
||||||
|
- [Development](#development)
|
||||||
|
- [Todo](#todo)
|
||||||
|
- [Kubernetes Users](#kubernetes-users)
|
||||||
|
|
||||||
|
<!-- tocstop -->
|
||||||
|
|
||||||
### Drone CI Plugin Config
|
### Drone CI Plugin Config
|
||||||
`0.8.x`
|
`0.8.x`
|
||||||
```yaml
|
```yaml
|
||||||
pipeline:
|
pipeline:
|
||||||
...
|
#...
|
||||||
notification:
|
notification:
|
||||||
image: lddsb/drone-dingtalk-message
|
image: lddsb/drone-dingtalk-message
|
||||||
token: your-group-bot-token
|
token: your-group-bot-token
|
||||||
type: markdown
|
type: markdown
|
||||||
```
|
```
|
||||||
|
|
||||||
`1.0.x`
|
`1.x`
|
||||||
```yaml
|
```yaml
|
||||||
kind: pipeline
|
|
||||||
name: default
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
...
|
#...
|
||||||
- name: notification
|
- name: notification
|
||||||
image: lddsb/drone-dingtalk-message
|
image: lddsb/drone-dingtalk-message
|
||||||
settings:
|
settings:
|
||||||
token: your-groupbot-token
|
token: your-groupbot-token
|
||||||
type: markdown
|
type: markdown
|
||||||
|
secret: your-secret-for-generate-sign
|
||||||
|
debug: true
|
||||||
|
```
|
||||||
|
|
||||||
|
`Use the "exec" type`
|
||||||
|
```yaml
|
||||||
|
kind: pipeline
|
||||||
|
type: exec
|
||||||
|
|
||||||
|
steps:
|
||||||
|
...
|
||||||
|
|
||||||
|
- name: notification
|
||||||
|
environment: # Using environment to pass parameters
|
||||||
|
PLUGIN_TOKEN:
|
||||||
|
from_secret: dingtalk_token
|
||||||
|
PLUGIN_TYPE: markdown
|
||||||
|
PLUGIN_DEBUG: false
|
||||||
|
PLUGIN_TPL: /data/drone/dingtalk/tpls/markdown.tpl # The actual location (absolute path) of the tpl.
|
||||||
|
commands:
|
||||||
|
- /data/drone/dingtalk/dingtalk-message # Location of the "dingtalk-message" file (absolute path)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Plugin Parameter Reference
|
### Plugin Parameter Reference
|
||||||
@@ -35,34 +67,103 @@ String. Access token for group bot. (you can get the access token when you add a
|
|||||||
|
|
||||||
String. Message type, plan support text, markdown, link and action card, but due to time issue, it's only support `markdown` and `text` now, and you can get the best experience by use markdown.
|
String. Message type, plan support text, markdown, link and action card, but due to time issue, it's only support `markdown` and `text` now, and you can get the best experience by use markdown.
|
||||||
|
|
||||||
`message_color`(when `type=markdown`)
|
`secret`
|
||||||
|
|
||||||
Boolean value. This option can change the title and commit message color if turn on.
|
String. Secret for generate sign.
|
||||||
|
|
||||||
`success_color`(when `message_color=true`)
|
`tpl`
|
||||||
|
|
||||||
|
String. Your custom `tpl`, it can be a local path, or a remote http link.
|
||||||
|
|
||||||
|
`debug`
|
||||||
|
Boolean. Debug mode.
|
||||||
|
|
||||||
|
`tips_title`
|
||||||
|
|
||||||
|
String. You can customize the title for the message tips, just work when message type is markdown.
|
||||||
|
|
||||||
|
`success_color`
|
||||||
|
|
||||||
String. You can customize the color for the `build success` message by this option, you should input a hex color, example: `008000`.
|
String. You can customize the color for the `build success` message by this option, you should input a hex color, example: `008000`.
|
||||||
|
|
||||||
`failure_color`(when `message_color=true`)
|
`failure_color`
|
||||||
|
|
||||||
String. You can customize the color for the `build success` message by this option, you should input a hex color, example: `FF0000`.
|
String. You can customize the color for the `build success` message by this option, you should input a hex color, example: `FF0000`.
|
||||||
|
|
||||||
`sha_link`(when `type=markdown`)
|
`success_pic`
|
||||||
|
|
||||||
Boolean value. This option can link the sha to your source page when it turn on.
|
|
||||||
|
|
||||||
`message_pic`(when `type=markdown`)
|
|
||||||
|
|
||||||
Boolean value. If this option turn on, it will embed a image into the message.
|
|
||||||
|
|
||||||
`success_pic`(when `message_pic=true`)
|
|
||||||
|
|
||||||
String. You can customize the picture for the `build success` message by this option.
|
String. You can customize the picture for the `build success` message by this option.
|
||||||
|
|
||||||
`failure_pic`(when `message_pic=true`)
|
`failure_pic`
|
||||||
|
|
||||||
String. You can customize the picture for the `build failure` message by this option.
|
String. You can customize the picture for the `build failure` message by this option.
|
||||||
|
|
||||||
|
`tpl_commit_branch_name`
|
||||||
|
|
||||||
|
String. You can customize the [TPL_COMMIT_BRANCH] by this configuration item.
|
||||||
|
|
||||||
|
`tpl_repo_short_name`
|
||||||
|
|
||||||
|
String. You can customize the [TPL_REPO_SHORT_NAME] by this configuration item.
|
||||||
|
|
||||||
|
`tpl_repo_full_name`
|
||||||
|
|
||||||
|
String. You can customize the [TPL_REPO_FULL_NAME] by this configuration item.
|
||||||
|
|
||||||
|
`tpl_build_status_success`
|
||||||
|
|
||||||
|
String. You can customize the [TPL_BUILD_STATUS] (when status=`success`) by this configuration item.
|
||||||
|
|
||||||
|
`tpl_build_status_failure`
|
||||||
|
|
||||||
|
String. You can customize the [TPL_BUILD_STATUS] (when status=`failure`) by this configuration item.
|
||||||
|
|
||||||
|
`msg_at_mobiles`
|
||||||
|
|
||||||
|
String. You want at's phone number in the group, if you need at multi phone numbers, you can use `,` to separate. (if you use markdown type, you need define the at content in your tpl file)
|
||||||
|
|
||||||
|
### TPL
|
||||||
|
> `tpl` won't work with message type `link` !!!
|
||||||
|
|
||||||
|
That's a good news, we support `tpl` now.This is an example for `markdown` message:
|
||||||
|
|
||||||
|
# [TPL_REPO_FULL_NAME] build [TPL_BUILD_STATUS], takes [TPL_BUILD_CONSUMING]s
|
||||||
|
@mobile1 @mobile2
|
||||||
|
[TPL_COMMIT_MSG]
|
||||||
|
|
||||||
|
[TPL_COMMIT_SHA]([TPL_COMMIT_LINK])
|
||||||
|
|
||||||
|
[[TPL_AUTHOR_NAME]([TPL_AUTHOR_EMAIL])](mailto:[TPL_AUTHOR_EMAIL])
|
||||||
|
|
||||||
|
[Click To The Build Detail Page [TPL_STATUS_EMOTICON)]]([TPL_BUILD_LINK])
|
||||||
|
You can write your own `tpl` what you want. The syntax of `tpl` is very simple, you can fill `tpl` with preset variables. It's a list of currently supported preset variables:
|
||||||
|
|
||||||
|
| Variable | Value |
|
||||||
|
| :-------------------: | :-------------------------------------------------: |
|
||||||
|
| [TPL_REPO_SHORT_NAME] | current repo name(bare name) |
|
||||||
|
| [TPL_REPO_FULL_NAME] | the full name(with group name) of current repo |
|
||||||
|
| [TPL_REPO_GROUP_NAME] | the group name of current repo |
|
||||||
|
| [TPL_REPO_OWNER_NAME] | the owner name of current repo |
|
||||||
|
| [TPL_REPO_REMOTE_URL] | the remote url of current repo |
|
||||||
|
| [TPL_BUILD_STATUS] | current build status(e.g., success, failure) |
|
||||||
|
| [TPL_BUILD_LINK] | current build link |
|
||||||
|
| [TPL_BUILD_EVENT] | current build event(e.g., push, pull request, etc.) |
|
||||||
|
| [TPL_BUILD_CONSUMING] | current build consuming, second |
|
||||||
|
| [TPL_COMMIT_SHA] | current commit sha |
|
||||||
|
| [TPL_COMMIT_REF] | current commit ref(e.g., refs/heads/master, etc.) |
|
||||||
|
| [TPL_COMMIT_LINK] | current commit remote url link |
|
||||||
|
| [TPL_COMMIT_BRANCH] | current branch name(e.g., dev, etc) |
|
||||||
|
| [TPL_COMMIT_MSG] | current commit message |
|
||||||
|
| [TPL_AUTHOR_NAME] | current commit author name |
|
||||||
|
| [TPL_AUTHOR_EMAIL] | current commit author email |
|
||||||
|
| [TPL_AUTHOR_USERNAME] | current commit author username |
|
||||||
|
| [TPL_AUTHOR_AVATAR] | current commit author avatar |
|
||||||
|
| [TPL_STATUS_PIC] | custom pic for build status |
|
||||||
|
| [TPL_STATUS_COLOR] | custom color for build status |
|
||||||
|
| [TPL_STATUS_EMOTICON] | custom emoticon for build status |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Screen Shot
|
### Screen Shot
|
||||||
- Send Success
|
- Send Success
|
||||||
|
|
||||||
@@ -88,28 +189,35 @@ String. You can customize the picture for the `build failure` message by this op
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
### Todo
|
|
||||||
|
|
||||||
- Multi-Type
|
|
||||||
- Multi-Lang
|
|
||||||
- More User Customization
|
|
||||||
|
|
||||||
|
|
||||||
### Development
|
### Development
|
||||||
|
We use `go mod` to manage dependencies, so it's easy to build.
|
||||||
|
|
||||||
- First get this repo
|
- get this repo
|
||||||
```shell
|
```shell
|
||||||
go get github.com/lddsb/drone-dingtalk-message
|
$ git clone https://github.com/lddsb/drone-dingtalk-message.git /path/to/you/want
|
||||||
```
|
|
||||||
- get dependent lib
|
|
||||||
```shell
|
|
||||||
dep ensure
|
|
||||||
```
|
```
|
||||||
- build
|
- build
|
||||||
```shell
|
```shell
|
||||||
cd $GOPATH/src/github.com/lddsb/drone-dingtalk-message && go build .
|
$ cd /path/to/you/want && GO111MODULE=on go build .
|
||||||
```
|
```
|
||||||
- run
|
- run
|
||||||
```shell
|
```shell
|
||||||
./drone-dingtalk-message -h
|
$ ./drone-dingtalk-message -h
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### TODO
|
||||||
|
It's sad, just support `text`, `markdown` and `link` type now.
|
||||||
|
- implement all message type
|
||||||
|
- i18N
|
||||||
|
- batch send
|
||||||
|
- retry(e.g., network error, etc.)
|
||||||
|
|
||||||
|
### Kubernetes Users
|
||||||
|
Attention kubernetes users, [CHANGELOG](CHANGELOG.md#124---2020-04-28).It's the available versions:
|
||||||
|
|
||||||
|
- `1.1`(always latest for `1.1.x`)
|
||||||
|
- `>=1.1.4`
|
||||||
|
- `1.2`(always latest for `1.2.x`)
|
||||||
|
- `>=1.2.4`
|
||||||
|
- latest(always latest)
|
||||||
|
|||||||
+226
@@ -0,0 +1,226 @@
|
|||||||
|
# Drone CI的钉钉群组机器人通知插件
|
||||||
|
[](https://github.com/lddsb/drone-dingtalk-message/actions?query=workflow%3A%22Publish+to+DockerHub+and+Github+Package%22) [](https://goreportcard.com/report/github.com/lddsb/drone-dingtalk-message) [](https://codecov.io/gh/lddsb/drone-dingtalk-message) [](https://app.dependabot.com/accounts/lddsb/repos/159822771) [](LICENSE)
|
||||||
|
|
||||||
|
|
||||||
|
<!-- toc -->
|
||||||
|
|
||||||
|
- [怎么使用本插件](#%E6%80%8E%E4%B9%88%E4%BD%BF%E7%94%A8%E6%9C%AC%E6%8F%92%E4%BB%B6)
|
||||||
|
- [插件参数](#%E6%8F%92%E4%BB%B6%E5%8F%82%E6%95%B0)
|
||||||
|
- [模版](#%E6%A8%A1%E7%89%88)
|
||||||
|
- [截图展示](#%E6%88%AA%E5%9B%BE%E5%B1%95%E7%A4%BA)
|
||||||
|
- [贡献代码](#%E8%B4%A1%E7%8C%AE%E4%BB%A3%E7%A0%81)
|
||||||
|
- [未来计划](#%E6%9C%AA%E6%9D%A5%E8%AE%A1%E5%88%92)
|
||||||
|
- [Kubernetes 用户请注意](#kubernetes-%E7%94%A8%E6%88%B7%E8%AF%B7%E6%B3%A8%E6%84%8F)
|
||||||
|
|
||||||
|
<!-- tocstop -->
|
||||||
|
|
||||||
|
### 怎么使用本插件
|
||||||
|
添加一个`step`到你的`.drone.yml`中,下面是例子:
|
||||||
|
|
||||||
|
`0.8.x`
|
||||||
|
```yaml
|
||||||
|
pipeline:
|
||||||
|
...
|
||||||
|
notification:
|
||||||
|
image: lddsb/drone-dingtalk-message
|
||||||
|
token: your-group-bot-token
|
||||||
|
type: markdown
|
||||||
|
```
|
||||||
|
|
||||||
|
`1.x`
|
||||||
|
```yaml
|
||||||
|
steps:
|
||||||
|
...
|
||||||
|
- name: notification
|
||||||
|
image: lddsb/drone-dingtalk-message
|
||||||
|
settings:
|
||||||
|
token: your-groupbot-token
|
||||||
|
type: markdown
|
||||||
|
secret: your-secret-for-generate-sign
|
||||||
|
debug: true
|
||||||
|
```
|
||||||
|
|
||||||
|
`命令行版本`
|
||||||
|
```yaml
|
||||||
|
kind: pipeline
|
||||||
|
type: exec
|
||||||
|
|
||||||
|
steps:
|
||||||
|
...
|
||||||
|
|
||||||
|
- name: 通知
|
||||||
|
environment: # 使用 environment 传递参数
|
||||||
|
PLUGIN_TOKEN:
|
||||||
|
from_secret: dingtalk_token
|
||||||
|
PLUGIN_TYPE: markdown
|
||||||
|
PLUGIN_DEBUG: false
|
||||||
|
PLUGIN_TPL: /data/drone/dingtalk/tpls/markdown.tpl # tpl 的实际位置(绝对路径)
|
||||||
|
commands:
|
||||||
|
- /data/drone/dingtalk/dingtalk-message # dingtalk-message 文件的位置(绝对路径)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 插件参数
|
||||||
|
`token`(必须)
|
||||||
|
|
||||||
|
你可以通过加入和创建一个群组来添加钉钉自定义机器人,添加自定义机器人完成后即可获得所需要的`access token`。
|
||||||
|
|
||||||
|
`type`(必须)
|
||||||
|
|
||||||
|
消息类型,因个人能力有限,目前仅支持`markdown`和`text`,其中,使用`markdown`可以获得最好的体验。
|
||||||
|
|
||||||
|
`secret`
|
||||||
|
|
||||||
|
如果你设置了`加签`,可以把你的`加签`密钥填入此项完成`加签`操作。
|
||||||
|
|
||||||
|
`tpl`
|
||||||
|
|
||||||
|
你可以通过该字段来自定义你的消息模版。该字段可以是一个本地路径也可以是一个远程的URL。
|
||||||
|
|
||||||
|
`debug`
|
||||||
|
|
||||||
|
通过该值可以打开`debug`模式,打印所有环境变量。
|
||||||
|
|
||||||
|
`tips_title`
|
||||||
|
|
||||||
|
你可以通过该字段自定义钉钉机器人的消息通知提醒标题。(注意,不是消息内容的标题,是收到钉钉机器人发的消息后,会有一个外显的标题)
|
||||||
|
|
||||||
|
`success_color`
|
||||||
|
|
||||||
|
你可以通过该字段自定义打包成功的颜色。比如:`008000`。
|
||||||
|
|
||||||
|
`failure_color`
|
||||||
|
|
||||||
|
你可以通过该字段自定义打包失败的颜色。比如:`FF0000`。
|
||||||
|
|
||||||
|
`success_pic`
|
||||||
|
|
||||||
|
你可以通过该字段自定义打包成功的图片。
|
||||||
|
|
||||||
|
`failure_pic`
|
||||||
|
|
||||||
|
字符串,你可以通过该字段自定义打包失败的图片。
|
||||||
|
|
||||||
|
`tpl_commit_branch_name`
|
||||||
|
|
||||||
|
你可以通过该字段自定义分支的名称,可以在模版中通过[TPL_COMMIT_BRANCH]来使用该值。
|
||||||
|
|
||||||
|
`tpl_repo_short_name`
|
||||||
|
|
||||||
|
你可以通过该字段自定义仓库的名字,可以在模版中通过[TPL_REPO_SHORT_NAME]来使用该值。
|
||||||
|
|
||||||
|
`tpl_repo_full_name`
|
||||||
|
|
||||||
|
你可以通过该字段自定义仓库的全名(包含组织名称),可以在模版中通过[TPL_REPO_FULL_NAME]来使用该值。
|
||||||
|
|
||||||
|
`tpl_build_status_success`
|
||||||
|
|
||||||
|
你可以通过该字段自定义运行成功状态的值,可以在模版中通过[TPL_BUILD_STATUS]来使用该值。(仅当前方`step`运行结果为成功时该值会生效)
|
||||||
|
|
||||||
|
`tpl_build_status_failure`
|
||||||
|
|
||||||
|
你可以通过该字段自定义运行失败状态的值,可以在模版中通过[TPL_BUILD_STATUS]来使用该值。(仅当前方`step`运行结果为失败时该值会生效)
|
||||||
|
|
||||||
|
`msg_at_mobiles`
|
||||||
|
|
||||||
|
你需要@的群成员的手机号,多个时用英文逗号(`,`)分隔。如过你使用的是 `markdown` 类型的消息,则需要在 `tpl` 文件中加入 `@手机号` 的内容。
|
||||||
|
|
||||||
|
### 模版
|
||||||
|
> `tpl` 对 `link` 类型的消息并不支持 !!!
|
||||||
|
|
||||||
|
感天动地,我们终于支持自定义模版了!下面是一个`markdown`的自定义模版例子:
|
||||||
|
|
||||||
|
# [TPL_REPO_FULL_NAME] build [TPL_BUILD_STATUS], takes [TPL_BUILD_CONSUMING]s
|
||||||
|
@mobile1 @mobile2
|
||||||
|
[TPL_COMMIT_MSG]
|
||||||
|
|
||||||
|
[TPL_COMMIT_SHA]([TPL_COMMIT_LINK])
|
||||||
|
|
||||||
|
[[TPL_AUTHOR_NAME]([TPL_AUTHOR_EMAIL])](mailto:[TPL_AUTHOR_EMAIL])
|
||||||
|
|
||||||
|
[Click To The Build Detail Page [TPL_STATUS_EMOTICON)]]([TPL_BUILD_LINK])
|
||||||
|
|
||||||
|
`mobile1` 和 `mobile2` 应该为钉钉对应的手机号码,可以放在自己想要放的位置。
|
||||||
|
|
||||||
|
你可以写自己喜欢的模版,终于不用再对默认模版发愁啦!并且模版的语法非常简单!比较可惜的是目前支持的变量还比较少,下面是当前支持的变量的列表:
|
||||||
|
|
||||||
|
| Variable | Value |
|
||||||
|
| :-------------------: | :-------------------------------------------------: |
|
||||||
|
| [TPL_REPO_SHORT_NAME] | 当前仓库的名称,比如本仓库 `drone-dingtalk-message` |
|
||||||
|
| [TPL_REPO_FULL_NAME] | 当前仓库的名称,比如本仓库 `lddsb/drone-dingtalk-message` |
|
||||||
|
| [TPL_REPO_GROUP_NAME] | 当前仓库的组织名称,比如本仓库 `lddsb` |
|
||||||
|
| [TPL_REPO_OWNER_NAME] | 当前仓库拥有者的名称 |
|
||||||
|
| [TPL_REPO_REMOTE_URL] | 当前仓库的远程地址 |
|
||||||
|
| [TPL_BUILD_STATUS] | 当前编译的状态(比如, success, failure) |
|
||||||
|
| [TPL_BUILD_LINK] | 当前编译的链接 |
|
||||||
|
| [TPL_BUILD_EVENT] | 触发当前编译的动作(比如, push, pull request等) |
|
||||||
|
| [TPL_BUILD_CONSUMING] | 当前编译耗时,单位秒 |
|
||||||
|
| [TPL_COMMIT_SHA] | 当前提交的sha |
|
||||||
|
| [TPL_COMMIT_REF] | 当前提交的ref(比如, refs/heads/master等) |
|
||||||
|
| [TPL_COMMIT_LINK] | 当前提交的远程地址 |
|
||||||
|
| [TPL_COMMIT_BRANCH] | 当前分之名称(比如, dev, master等) |
|
||||||
|
| [TPL_COMMIT_MSG] | 当前提交的信息 |
|
||||||
|
| [TPL_AUTHOR_NAME] | 当前提交作者名称 |
|
||||||
|
| [TPL_AUTHOR_EMAIL] | 当前提交作者邮箱地址 |
|
||||||
|
| [TPL_AUTHOR_USERNAME] | 当前提交作者的用户名 |
|
||||||
|
| [TPL_AUTHOR_AVATAR] | 当前提交作者的头像 |
|
||||||
|
| [TPL_STATUS_PIC] | 根据编译状态显示不同的图片 |
|
||||||
|
| [TPL_STATUS_COLOR] | 根据编译状态显示不同的颜色 |
|
||||||
|
| [TPL_STATUS_EMOTICON] | 根据编译状态显示不同的表情,比如 `:)` `:(` |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 截图展示
|
||||||
|
- 发送成功(Drone Web)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- 忘记填写Access Token(Drone Web)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- 忘记填写消息类型或者不支持的消息类型
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- 默认的`markdown`消息
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- 带颜色和链接的`markdown`消息
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- 带颜色、链接和图片的`markdown`消息
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
### 贡献代码
|
||||||
|
本项目使用了`go mod`来管理依赖,因此要编译本项目相当简单。
|
||||||
|
|
||||||
|
- 先把项目代码拷贝到本地
|
||||||
|
```shell
|
||||||
|
$ git clone https://github.com/lddsb/drone-dingtalk-message.git /path/to/you/want
|
||||||
|
```
|
||||||
|
- 然后直接执行编译即可
|
||||||
|
```shell
|
||||||
|
$ cd /path/to/you/want && GO111MODULE=on go build .
|
||||||
|
```
|
||||||
|
- 跑个`help`
|
||||||
|
```shell
|
||||||
|
$ ./drone-dingtalk-message -h
|
||||||
|
```
|
||||||
|
|
||||||
|
### 未来计划
|
||||||
|
目前仅支持 `text`, `markdown` 以及 `link` 类型的消息,建议使用`markdown`类型。
|
||||||
|
- 实现更多的消息类型
|
||||||
|
- i18N国际化直接翻译环境变量
|
||||||
|
- 批量发送给多个群机器人
|
||||||
|
- 失败重试机制
|
||||||
|
|
||||||
|
### Kubernetes 用户请注意
|
||||||
|
因为`Drone CI` [官方缺陷](https://docs.drone.io/runner/kubernetes/overview) ,所以较早版本将无法正常获取到需要用到的变量,会导致部分功能异常。为了能正常使用,所以请使用以下版本:
|
||||||
|
- `1.1`(总会是`1.1.x`的最新版本)
|
||||||
|
- `>=1.1.4`
|
||||||
|
- `1.2`(总会是`1.2.x`的最新版本)
|
||||||
|
- `>=1.2.4`
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
module github.com/lddsb/drone-dingtalk-message
|
||||||
|
|
||||||
|
go 1.12
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/joho/godotenv v1.5.1
|
||||||
|
github.com/lddsb/dingtalk-webhook v0.0.5
|
||||||
|
github.com/urfave/cli v1.22.14
|
||||||
|
)
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
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/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
|
github.com/lddsb/dingtalk-webhook v0.0.5 h1:EOSXvcpN4IC7fXSAheI3OLJwwOGEPlGDV7xje1fQuJo=
|
||||||
|
github.com/lddsb/dingtalk-webhook v0.0.5/go.mod h1:dwNU75Sog87wJXAFcY5mDFM7eW4hIdX7bNemrN92pH0=
|
||||||
|
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/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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk=
|
||||||
|
github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
@@ -4,18 +4,21 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
_ "github.com/joho/godotenv/autoload"
|
"github.com/joho/godotenv"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Version = "0.1.1202"
|
// Version of cli
|
||||||
|
var Version = "0.2.1219"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
app := cli.NewApp()
|
app := cli.NewApp()
|
||||||
app.Name = "Drone Dingtalk Message Plugin"
|
app.Name = "Drone DingTalk Message Plugin"
|
||||||
app.Usage = "Sending message to Dingtalk group by robot using webhook"
|
app.Usage = "Sending message to DingTalk group by robot using WebHook"
|
||||||
app.Copyright = "© 2018 Dee Luo"
|
year := time.Now().Year()
|
||||||
|
app.Copyright = fmt.Sprintf("© 2018-%d Dee Luo", year)
|
||||||
app.Authors = []cli.Author{
|
app.Authors = []cli.Author{
|
||||||
{
|
{
|
||||||
Name: "Dee Luo",
|
Name: "Dee Luo",
|
||||||
@@ -25,47 +28,45 @@ func main() {
|
|||||||
app.Action = run
|
app.Action = run
|
||||||
app.Version = Version
|
app.Version = Version
|
||||||
app.Flags = []cli.Flag{
|
app.Flags = []cli.Flag{
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "config.debug,debug",
|
||||||
|
Usage: "debug mode",
|
||||||
|
EnvVar: "PLUGIN_DEBUG",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "config.tips.title",
|
||||||
|
Usage: "customize the tips title",
|
||||||
|
EnvVar: "PLUGIN_TIPS_TITLE",
|
||||||
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "config.token,access_token,token",
|
Name: "config.token,access_token,token",
|
||||||
Usage: "dingtalk webhook access token",
|
Usage: "DingTalk webhook access token",
|
||||||
EnvVar: "PLUGIN_ACCESS_TOKEN,PLUGIN_TOKEN",
|
EnvVar: "PLUGIN_ACCESS_TOKEN,PLUGIN_TOKEN",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "config.lang",
|
Name: "config.secret,secret",
|
||||||
Value: "zh_CN",
|
Usage: "DingTalk WebHook secret for generate sign",
|
||||||
Usage: "the lang display (zh_CN or en_US, zh_CN is default)",
|
EnvVar: "PLUGIN_SECRET",
|
||||||
EnvVar: "PLUGIN_LANG",
|
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "config.message.type,msg_type,message_type,type",
|
Name: "config.message.type,message_type,type",
|
||||||
Usage: "dingtalk message type, like text, markdown, action card, link and feed card...",
|
Usage: "DingTalk message type, like text, markdown, action card, link and feed card...",
|
||||||
EnvVar: "PLUGIN_MSG_TYPE,PLUGIN_TYPE,PLUGIN_MESSAGE_TYPE",
|
EnvVar: "PLUGIN_MSG_TYPE,PLUGIN_TYPE,PLUGIN_MESSAGE_TYPE",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "config.message.at.all",
|
Name: "config.message.at.all,at.all",
|
||||||
Usage: "at all in a message(only text and markdown type message can at)",
|
Usage: "at all in a message(only text and markdown type message can at)",
|
||||||
EnvVar: "PLUGIN_MSG_AT_ALL",
|
EnvVar: "PLUGIN_MSG_AT_ALL",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "config.message.at.mobiles",
|
Name: "config.message.at.mobiles,mobiles",
|
||||||
Usage: "at someone in a dingtalk group need this guy bind's mobile",
|
Usage: "at someone in a DingTalk group need this guy bind's mobile",
|
||||||
EnvVar: "PLUGIN_MSG_AT_MOBILES",
|
EnvVar: "PLUGIN_MSG_AT_MOBILES",
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "drone",
|
|
||||||
Usage: "indicates the runtime environment is Drone",
|
|
||||||
EnvVar: "DRONE",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "branch",
|
Name: "commit.author.username",
|
||||||
Usage: "providers the branch for the current build",
|
Usage: "providers the author username for the current commit",
|
||||||
EnvVar: "DRONE_BRANCH",
|
EnvVar: "DRONE_COMMIT_AUTHOR",
|
||||||
},
|
|
||||||
// commit args start
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "remote.url",
|
|
||||||
Usage: "git remote url",
|
|
||||||
EnvVar: "DRONE_REMOTE_URL",
|
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "commit.author.avatar",
|
Name: "commit.author.avatar",
|
||||||
@@ -98,160 +99,50 @@ func main() {
|
|||||||
Usage: "providers the commit message for the current build",
|
Usage: "providers the commit message for the current build",
|
||||||
EnvVar: "DRONE_COMMIT_MESSAGE",
|
EnvVar: "DRONE_COMMIT_MESSAGE",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
|
||||||
Name: "commit.ref",
|
|
||||||
Usage: "providers the reference for the current build",
|
|
||||||
EnvVar: "DRONE_COMMIT_REF",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "commit.sha",
|
Name: "commit.sha",
|
||||||
Usage: "providers the commit sha for the current build",
|
Usage: "providers the commit sha for the current build",
|
||||||
EnvVar: "DRONE_COMMIT_SHA",
|
EnvVar: "DRONE_COMMIT_SHA",
|
||||||
},
|
},
|
||||||
// commit args end
|
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "git.url.http",
|
Name: "commit.ref",
|
||||||
Usage: "providers the repository git+http url",
|
Usage: "provider the commit ref for the current build",
|
||||||
EnvVar: "DRONE_GIT_HTTP_URL",
|
EnvVar: "DRONE_COMMIT_REF",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "git.url.ssh",
|
Name: "repo.full.name",
|
||||||
Usage: "providers the repository git+ssh url",
|
|
||||||
EnvVar: "DRONE_GIT_SSH_URL",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "machine",
|
|
||||||
Usage: "providers the Drone agent hostname",
|
|
||||||
EnvVar: "DRONE_MACHINE",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "pull.request",
|
|
||||||
Usage: "providers the pull request number for the current build.This value is only set if the build event is of type pull request",
|
|
||||||
EnvVar: "DRONE_PULL_REQUEST",
|
|
||||||
},
|
|
||||||
// repo args start
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "repo.fullname",
|
|
||||||
Usage: "providers the full name of the repository",
|
Usage: "providers the full name of the repository",
|
||||||
EnvVar: "DRONE_REPO",
|
EnvVar: "DRONE_REPO",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
|
||||||
Name: "repo.owner",
|
|
||||||
Usage: "repository owner",
|
|
||||||
EnvVar: "DRONE_REPO_OWNER",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "repo.branch",
|
|
||||||
Usage: "providers the default repository branch(e.g.master)",
|
|
||||||
EnvVar: "DRONE_REPO_BRANCH",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "repo.link",
|
|
||||||
Usage: "providers the repository http link",
|
|
||||||
EnvVar: "DRONE_REPO_LINK",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "repo.name",
|
Name: "repo.name",
|
||||||
Usage: "providers the repository name",
|
Usage: "provider the name of the repository",
|
||||||
EnvVar: "DRONE_REPO_NAME",
|
EnvVar: "DRONE_REPO_NAME",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "repo.avatar",
|
Name: "repo.group",
|
||||||
Usage: "repository avatar",
|
Usage: "provider the group of the repository",
|
||||||
EnvVar: "DRONE_REPO_AVATAR",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "repo.namespace",
|
|
||||||
Usage: "providers the repository namespace(e.g. account owner)",
|
|
||||||
EnvVar: "DRONE_REPO_NAMESPACE",
|
EnvVar: "DRONE_REPO_NAMESPACE",
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "repo.private",
|
|
||||||
Usage: "indicates the repository is public or private",
|
|
||||||
EnvVar: "DRONE_REPO_PRIVATE",
|
|
||||||
},
|
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "repo.trusted",
|
|
||||||
Usage: "repository is trusted",
|
|
||||||
EnvVar: "DRONE_REPO_TRUSTED",
|
|
||||||
},
|
|
||||||
// repo args end
|
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "runner.host",
|
Name: "repo.remote.url",
|
||||||
Usage: "provider are Drone agent hostname",
|
Usage: "provider the remote url of the repository",
|
||||||
EnvVar: "DRONE_RUNNER_HOST",
|
EnvVar: "DRONE_REMOTE_URL",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "runner.hostname",
|
Name: "repo.owner",
|
||||||
Usage: "providers the Drone agent hostname",
|
Usage: "provider the owner of the repository",
|
||||||
EnvVar: "DRONE_RUNNER_HOSTNAME",
|
EnvVar: "DRONE_REPO_OWNER",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.Uint64Flag{
|
||||||
Name: "runner.platform",
|
Name: "stage.started",
|
||||||
Usage: "providers the Drone agent os and architecture",
|
Usage: "stage started ",
|
||||||
EnvVar: "DRONE_RUNNER_PLATFORM",
|
EnvVar: "DRONE_STAGE_STARTED",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.Uint64Flag{
|
||||||
Name: "runner.label",
|
Name: "stage.finished",
|
||||||
Usage: "404 not found",
|
Usage: "stage finished",
|
||||||
EnvVar: "DRONE_RUNNER_LABEL",
|
EnvVar: "DRONE_STAGE_FINISHED",
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "source.branch",
|
|
||||||
Usage: "providers the source branch for a pull request",
|
|
||||||
EnvVar: "DRONE_SOURCE_BRANCH",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "target.branch",
|
|
||||||
Usage: "providers the target branch for a pull request",
|
|
||||||
EnvVar: "DRONE_TARGET_BRANCH",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "system.host",
|
|
||||||
Usage: "providers the Drone server hostname",
|
|
||||||
EnvVar: "DRONE_SYSTEM_HOST",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "system.hostname",
|
|
||||||
Usage: "providers the Drone server hostname",
|
|
||||||
EnvVar: "DRONE_SYSTEM_HOSTNAME",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "system.version",
|
|
||||||
Usage: "providers the Drone server version",
|
|
||||||
EnvVar: "DRONE_SYSTEM_VERSION",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "tag",
|
|
||||||
Usage: "providers the tag name for the current build.This value is only set if the build event is of type tag",
|
|
||||||
EnvVar: "DRONE_TAG",
|
|
||||||
},
|
|
||||||
// build args start
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "build.event",
|
|
||||||
Value: "push",
|
|
||||||
Usage: "build event",
|
|
||||||
EnvVar: "DRONE_BUILD_EVENT",
|
|
||||||
},
|
|
||||||
cli.IntFlag{
|
|
||||||
Name: "build.number",
|
|
||||||
Usage: "build number",
|
|
||||||
EnvVar: "DRONE_BUILD_NUMBER",
|
|
||||||
},
|
|
||||||
cli.IntFlag{
|
|
||||||
Name: "build.created",
|
|
||||||
Usage: "build created",
|
|
||||||
EnvVar: "DRONE_BUILD_CREATED",
|
|
||||||
},
|
|
||||||
cli.IntFlag{
|
|
||||||
Name: "build.started",
|
|
||||||
Usage: "build started",
|
|
||||||
EnvVar: "DRONE_BUILD_STARTED",
|
|
||||||
},
|
|
||||||
cli.IntFlag{
|
|
||||||
Name: "build.finished",
|
|
||||||
Usage: "build finished",
|
|
||||||
EnvVar: "DRONE_BUILD_FINISHED",
|
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "build.status",
|
Name: "build.status",
|
||||||
@@ -265,130 +156,168 @@ func main() {
|
|||||||
EnvVar: "DRONE_BUILD_LINK",
|
EnvVar: "DRONE_BUILD_LINK",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "build.deploy",
|
Name: "build.event",
|
||||||
Usage: "build deployment target",
|
Usage: "build event",
|
||||||
EnvVar: "DRONE_DEPLOY_TO",
|
EnvVar: "DRONE_BUILD_EVENT",
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
cli.Uint64Flag{
|
||||||
Name: "yaml.verified",
|
Name: "build.started",
|
||||||
Usage: "build yaml is verified",
|
Usage: "build started",
|
||||||
EnvVar: "DRONE_YAML_VERIFIED",
|
EnvVar: "DRONE_BUILD_STARTED",
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
cli.Uint64Flag{
|
||||||
Name: "yaml.signed",
|
Name: "build.finished",
|
||||||
Usage: "build yaml is signed",
|
Usage: "build finished",
|
||||||
EnvVar: "DRONE_YAML_SIGNED",
|
EnvVar: "DRONE_BUILD_FINISHED",
|
||||||
},
|
|
||||||
// build args end
|
|
||||||
cli.Float64Flag{
|
|
||||||
Name: "job.started",
|
|
||||||
Usage: "job started",
|
|
||||||
EnvVar: "DRONE_JOB_STARTED",
|
|
||||||
},
|
|
||||||
cli.Float64Flag{
|
|
||||||
Name: "job.finished",
|
|
||||||
Usage: "job finished",
|
|
||||||
EnvVar: "DRONE_JOB_FINISHED",
|
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "ci.repo.link",
|
Name: "tpl.build.status.success",
|
||||||
Usage: "ci repo link",
|
Usage: "tpl.build status for replace success",
|
||||||
EnvVar: "CI_REPO_LINK",
|
EnvVar: "TPL_BUILD_STATUS_SUCCESS, PLUGIN_TPL_BUILD_STATUS_SUCCESS",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "config.success.pic.url",
|
Name: "tpl.build.status.failure",
|
||||||
Usage: "config success picture url",
|
Usage: "tpl.build status for replace failure",
|
||||||
|
EnvVar: "TPL_BUILD_STATUS_FAILURE, PLUGIN_TPL_BUILD_STATUS_FAILURE",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "custom.pic.url.success",
|
||||||
|
Usage: "custom success picture url",
|
||||||
EnvVar: "SUCCESS_PICTURE_URL,PLUGIN_SUCCESS_PIC",
|
EnvVar: "SUCCESS_PICTURE_URL,PLUGIN_SUCCESS_PIC",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "config.failure.pic.url",
|
Name: "custom.pic.url.failure",
|
||||||
Usage: "config failure picture url",
|
Usage: "custom failure picture url",
|
||||||
EnvVar: "FAILURE_PICTURE_URL,PLUGIN_FAILURE_PIC",
|
EnvVar: "FAILURE_PICTURE_URL,PLUGIN_FAILURE_PIC",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "config.success.color",
|
Name: "custom.color.success",
|
||||||
Usage: "config success color for title in markdown",
|
Usage: "custom success color for title in markdown",
|
||||||
EnvVar: "SUCCESS_COLOR,PLUGIN_SUCCESS_COLOR",
|
EnvVar: "SUCCESS_COLOR,PLUGIN_SUCCESS_COLOR",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "config.failure.color",
|
Name: "custom.color.failure",
|
||||||
Usage: "config failure color for title in markdown",
|
Usage: "custom failure color for title in markdown",
|
||||||
EnvVar: "FAILURE_COLOR,PLUGIN_FAILURE_COLOR",
|
EnvVar: "FAILURE_COLOR,PLUGIN_FAILURE_COLOR",
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
cli.StringFlag{
|
||||||
Name: "config.message.color",
|
Name: "custom.tpl,tpl",
|
||||||
Usage: "configure the message with color or not",
|
Usage: "custom tpl",
|
||||||
EnvVar: "PLUGIN_COLOR,PLUGIN_MESSAGE_COLOR",
|
EnvVar: "PLUGIN_TPL,PLUGIN_CUSTOM_TPL",
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
cli.StringFlag{
|
||||||
Name: "config.message.pic",
|
Name: "tpl.repo.full.name",
|
||||||
Usage: "configure the message with picture or not",
|
Usage: "tpl custom repo full name",
|
||||||
EnvVar: "PLUGIN_PIC,PLUGIN_MESSAGE_PIC",
|
EnvVar: "PLUGIN_TPL_REPO_FULL_NAME,TPL_REPO_FULL_NAME",
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
cli.StringFlag{
|
||||||
Name: "config.message.sha.link",
|
Name: "tpl.repo.short.name",
|
||||||
Usage: "link sha source page or not",
|
Usage: "tpl custom repo short name",
|
||||||
EnvVar: "PLUGIN_SHA_LINK,PLUGIN_MESSAGE_SHA_LINK",
|
EnvVar: "PLUGIN_TPL_REPO_SHORT_NAME,TPL_REPO_SHORT_NAME",
|
||||||
},
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "tpl.commit.branch.name",
|
||||||
|
Usage: "tpl custom commit branch name",
|
||||||
|
EnvVar: "PLUGIN_TPL_COMMIT_BRANCH_NAME,TPL_COMMIT_BRANCH_NAME",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "custom.started,started",
|
||||||
|
Usage: "started custom env name, eg., BUILD_STARTED",
|
||||||
|
EnvVar: "PLUGIN_CUSTOM_STARTED",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "custom.finished,finished",
|
||||||
|
Usage: "finished custom env name, eg., BUILD_FINISHED",
|
||||||
|
EnvVar: "PLUGIN_CUSTOM_FINISHED",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// kubernetes runner patch
|
||||||
|
if _, err := os.Stat("/run/drone/env"); err == nil {
|
||||||
|
godotenv.Overload("/run/drone/env")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := app.Run(os.Args); nil != err {
|
if err := app.Run(os.Args); nil != err {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// run with args
|
// run with args
|
||||||
func run(c *cli.Context) {
|
func run(c *cli.Context) {
|
||||||
plugin := Plugin{
|
plugin := Plugin{
|
||||||
// repo info
|
Drone: Drone{
|
||||||
Repo: Repo{
|
// repo info
|
||||||
FullName: c.String("repo.fullname"),
|
Repo: Repo{
|
||||||
Owner: c.String("repo.owner"),
|
ShortName: c.String("repo.name"),
|
||||||
Name: c.String("repo.name"),
|
GroupName: c.String("repo.group"),
|
||||||
},
|
OwnerName: c.String("repo.owner"),
|
||||||
// build info
|
RemoteURL: c.String("repo.remote.url"),
|
||||||
Build: Build{
|
FullName: c.String("repo.full.name"),
|
||||||
Action: c.String("build.action"),
|
},
|
||||||
Number: c.Int("build.number"),
|
// build info
|
||||||
Started: c.Float64("build.started"),
|
Build: Build{
|
||||||
Created: c.Float64("build.created"),
|
Status: c.String("build.status"),
|
||||||
Event: c.String("build.event"),
|
Link: c.String("build.link"),
|
||||||
Status: c.String("build.status"),
|
Event: c.String("build.event"),
|
||||||
Link: c.String("build.link"),
|
StartAt: c.Uint64("build.started"),
|
||||||
},
|
FinishedAt: c.Uint64("build.finished"),
|
||||||
Commit: Commit{
|
},
|
||||||
Sha: c.String("commit.sha"),
|
Commit: Commit{
|
||||||
Branch: c.String("commit.branch"),
|
Sha: c.String("commit.sha"),
|
||||||
Message: c.String("commit.message"),
|
Branch: c.String("commit.branch"),
|
||||||
Link: c.String("commit.link"),
|
Message: c.String("commit.message"),
|
||||||
Authors: struct {
|
Link: c.String("commit.link"),
|
||||||
Avatar string
|
Author: CommitAuthor{
|
||||||
Email string
|
Avatar: c.String("commit.author.avatar"),
|
||||||
Name string
|
Email: c.String("commit.author.email"),
|
||||||
}{
|
Name: c.String("commit.author.name"),
|
||||||
Avatar: c.String("commit.author.avatar"),
|
Username: c.String("commit.author.username"),
|
||||||
Email: c.String("commit.author.email"),
|
},
|
||||||
Name: c.String("commit.author.name"),
|
},
|
||||||
|
Stage: Stage{
|
||||||
|
StartedAt: c.Uint64("stage.started"),
|
||||||
|
FinishedAt: c.Uint64("stage.finished"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// custom config
|
// custom config
|
||||||
Config: Config{
|
Config: Config{
|
||||||
AccessToken: c.String("config.token"),
|
AccessToken: c.String("config.token"),
|
||||||
Lang: c.String("config.lang"),
|
Secret: c.String("config.secret"),
|
||||||
IsAtALL: c.Bool("config.message.at.all"),
|
IsAtALL: c.Bool("config.message.at.all"),
|
||||||
MsgType: c.String("config.message.type"),
|
MsgType: c.String("config.message.type"),
|
||||||
Mobiles: c.String("config.message.at.mobiles"),
|
Mobiles: c.String("config.message.at.mobiles"),
|
||||||
SuccessPicUrl: c.String("config.success.pic.url"),
|
Debug: c.Bool("config.debug"),
|
||||||
FailurePicUrl: c.String("config.failure.pic.url"),
|
TipsTitle: c.String("config.tips.title"),
|
||||||
SuccessColor: c.String("config.success.color"),
|
|
||||||
FailureColor: c.String("config.failure.color"),
|
|
||||||
WithColor: c.Bool("config.message.color"),
|
|
||||||
WithPic: c.Bool("config.message.pic"),
|
|
||||||
LinkSha: c.Bool("config.message.sha.link"),
|
|
||||||
},
|
},
|
||||||
CI: CI{
|
Custom: Custom{
|
||||||
RepoLink: c.String("ci.repo.link"),
|
Pic: Pic{
|
||||||
|
SuccessPicURL: c.String("custom.pic.url.success"),
|
||||||
|
FailurePicURL: c.String("custom.pic.url.failure"),
|
||||||
|
},
|
||||||
|
Color: Color{
|
||||||
|
SuccessColor: c.String("custom.color.success"),
|
||||||
|
FailureColor: c.String("custom.color.failure"),
|
||||||
|
},
|
||||||
|
Tpl: c.String("custom.tpl"),
|
||||||
|
Consuming: Consuming{
|
||||||
|
StartedEnv: c.String("custom.started"),
|
||||||
|
FinishedEnv: c.String("custom.finished"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Tpl: Tpl{
|
||||||
|
Repo: TplRepo{
|
||||||
|
FullName: c.String("tpl.repo.full.name"),
|
||||||
|
ShortName: c.String("tpl.repo.short.name"),
|
||||||
|
},
|
||||||
|
Commit: TplCommit{
|
||||||
|
Branch: c.String("tpl.commit.branch.name"),
|
||||||
|
},
|
||||||
|
Build: TplBuild{
|
||||||
|
Status: Status{
|
||||||
|
Success: c.String("tpl.build.status.success"),
|
||||||
|
Failure: c.String("tpl.build.status.failure"),
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,258 +3,383 @@ package main
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
webhook "github.com/lddsb/dingtalk-webhook"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// repo base info
|
// Repo repo base info
|
||||||
Repo struct {
|
Repo struct {
|
||||||
Owner string // providers the repository owner name
|
ShortName string // short name
|
||||||
Name string // providers the repository name
|
GroupName string // group name
|
||||||
Branch string // providers the default repository branch(e.g.master)
|
FullName string // repository full name
|
||||||
Link string // providers the repository http link
|
OwnerName string // repo owner
|
||||||
NameSpace string // providers the repository namespace(e.g.account owner)
|
RemoteURL string // repo remote url
|
||||||
Private bool // indicates the repository is public or private
|
|
||||||
Visibility string // providers the repository visibility level.Possible values are public,private and internal
|
|
||||||
SCM string // providers the repository version control system
|
|
||||||
FullName string // repository full name
|
|
||||||
}
|
}
|
||||||
// build info
|
|
||||||
|
// Build info
|
||||||
Build struct {
|
Build struct {
|
||||||
Action string // document description not found
|
Status string // providers the current build status
|
||||||
Created float64 // providers the date and time when the build was created in the system
|
Link string // providers the current build link
|
||||||
Event string // providers the current build event
|
Event string // trigger event
|
||||||
Number int // providers the current build number
|
StartAt uint64 // build start at ( unix timestamp )
|
||||||
Started float64 // providers the date and time when the build was started
|
FinishedAt uint64 // build finish at ( unix timestamp )
|
||||||
Status string // providers the current build status
|
|
||||||
Link string // providers the current build link
|
|
||||||
}
|
}
|
||||||
// commit info
|
|
||||||
|
// Commit info
|
||||||
Commit struct {
|
Commit struct {
|
||||||
After string // providers the commit sha for the current build
|
|
||||||
Author string // providers the author username for the current commit
|
|
||||||
Before string // providers the parent commit sha for the current build
|
|
||||||
Branch string // providers the branch for the current commit
|
Branch string // providers the branch for the current commit
|
||||||
Link string // providers the http link to the current commit in the remote source code management system(e.g.GitHub)
|
Link string // providers the http link to the current commit in the remote source code management system(e.g.GitHub)
|
||||||
Message string // providers the commit message for the current build
|
Message string // providers the commit message for the current build
|
||||||
Ref string // providers the reference for the current build
|
|
||||||
Sha string // providers the commit sha for the current build
|
Sha string // providers the commit sha for the current build
|
||||||
// repo author info
|
Ref string // commit ref
|
||||||
Authors struct {
|
Author CommitAuthor
|
||||||
Avatar string // providers the author avatar for the current commit
|
|
||||||
Email string // providers the author email for the current commit
|
|
||||||
Name string // providers the author name for the current commit
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// git url info
|
|
||||||
Git struct {
|
// Stage drone stage env
|
||||||
HttpUrl string // providers the repository git+http url
|
Stage struct {
|
||||||
SSHUrl string // providers the repository git+ssh url
|
StartedAt uint64
|
||||||
|
FinishedAt uint64
|
||||||
}
|
}
|
||||||
// Drone runner info
|
|
||||||
Runner struct {
|
// CommitAuthor commit author info
|
||||||
Host string // providers the Drone agent hostname
|
CommitAuthor struct {
|
||||||
Hostname string // providers the Drone agent hostname
|
Avatar string // providers the author avatar for the current commit
|
||||||
Platform string // providers the Drone agent os and architecture
|
Email string // providers the author email for the current commit
|
||||||
Label string // document description not found
|
Name string // providers the author name for the current commit
|
||||||
|
Username string // the author username for the current commit
|
||||||
}
|
}
|
||||||
// Drone system info
|
|
||||||
System struct {
|
// Drone drone info
|
||||||
Host string // providers the Drone server hostname
|
Drone struct {
|
||||||
Hostname string // providers the Drone server hostname
|
Repo Repo
|
||||||
Version string // providers the Drone server version
|
Build Build
|
||||||
|
Commit Commit
|
||||||
|
Stage Stage
|
||||||
}
|
}
|
||||||
// Drone CI Info
|
|
||||||
CI struct {
|
// Config plugin private config
|
||||||
RepoLink string
|
|
||||||
}
|
|
||||||
// plugin private config
|
|
||||||
Config struct {
|
Config struct {
|
||||||
AccessToken string
|
Debug bool
|
||||||
Message string
|
AccessToken string
|
||||||
Lang string
|
Secret string
|
||||||
IsAtALL bool
|
IsAtALL bool
|
||||||
Mobiles string
|
Mobiles string
|
||||||
Username string
|
Username string
|
||||||
AvatarURL string
|
MsgType string
|
||||||
MsgType string
|
TipsTitle string
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageConfig DingTalk message struct
|
||||||
|
MessageConfig struct {
|
||||||
|
ActionCard ActionCard
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActionCard action card message struct
|
||||||
|
ActionCard struct {
|
||||||
LinkUrls string
|
LinkUrls string
|
||||||
LinkTitles string
|
LinkTitles string
|
||||||
HideAvatar bool
|
HideAvatar bool
|
||||||
BtnOrientation bool
|
BtnOrientation bool
|
||||||
PicURL string
|
|
||||||
MsgURL string
|
|
||||||
SuccessPicUrl string
|
|
||||||
FailurePicUrl string
|
|
||||||
SuccessColor string
|
|
||||||
FailureColor string
|
|
||||||
WithColor bool
|
|
||||||
WithPic bool
|
|
||||||
LinkSha bool
|
|
||||||
}
|
}
|
||||||
// plugin all config
|
|
||||||
|
// Pic extra config for pic
|
||||||
|
Pic struct {
|
||||||
|
SuccessPicURL string
|
||||||
|
FailurePicURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Color extra config for color
|
||||||
|
Color struct {
|
||||||
|
SuccessColor string
|
||||||
|
FailureColor string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plugin plugin all config
|
||||||
Plugin struct {
|
Plugin struct {
|
||||||
CI CI
|
Tpl Tpl
|
||||||
Git Git
|
Drone Drone
|
||||||
Runner Runner
|
|
||||||
System System
|
|
||||||
Commit Commit
|
|
||||||
Repo Repo
|
|
||||||
Build Build
|
|
||||||
Config Config
|
Config Config
|
||||||
WebHook *WebHook
|
Custom Custom
|
||||||
|
Message MessageConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom user custom env
|
||||||
|
Custom struct {
|
||||||
|
Tpl string
|
||||||
|
Color Color
|
||||||
|
Pic Pic
|
||||||
|
Consuming Consuming
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tpl base
|
||||||
|
Tpl struct {
|
||||||
|
Repo TplRepo
|
||||||
|
Commit TplCommit
|
||||||
|
Build TplBuild
|
||||||
|
}
|
||||||
|
|
||||||
|
// TplRepo TPL repo
|
||||||
|
TplRepo struct {
|
||||||
|
FullName string
|
||||||
|
ShortName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// TplCommit TPL commit
|
||||||
|
TplCommit struct {
|
||||||
|
Branch string
|
||||||
|
}
|
||||||
|
|
||||||
|
// TplBuild TPL build
|
||||||
|
TplBuild struct {
|
||||||
|
Status Status
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status status
|
||||||
|
Status struct {
|
||||||
|
Success string
|
||||||
|
Failure string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consuming custom consuming env
|
||||||
|
Consuming struct {
|
||||||
|
StartedEnv string
|
||||||
|
FinishedEnv string
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Exec execute WebHook
|
||||||
func (p *Plugin) Exec() error {
|
func (p *Plugin) Exec() error {
|
||||||
log.Println("start execute sending...")
|
if p.Config.Debug {
|
||||||
if 0 == len(p.Config.AccessToken) {
|
for _, e := range os.Environ() {
|
||||||
msg := "missing dingtalk access token"
|
log.Println(e)
|
||||||
log.Println(msg)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if "" == p.Config.AccessToken {
|
||||||
|
msg := "missing DingTalk access token"
|
||||||
return errors.New(msg)
|
return errors.New(msg)
|
||||||
}
|
}
|
||||||
log.Println("access token pass...")
|
|
||||||
p.WebHook = NewWebHook(p.Config.AccessToken)
|
tpl, err := p.getMessage()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Config.TipsTitle == "" {
|
||||||
|
p.Config.TipsTitle = "you have a new message"
|
||||||
|
}
|
||||||
|
|
||||||
|
newWebHook := webhook.NewWebHook(p.Config.AccessToken)
|
||||||
|
|
||||||
|
// add sign
|
||||||
|
if "" != p.Config.Secret {
|
||||||
|
newWebHook.Secret = p.Config.Secret
|
||||||
|
}
|
||||||
|
|
||||||
mobiles := strings.Split(p.Config.Mobiles, ",")
|
mobiles := strings.Split(p.Config.Mobiles, ",")
|
||||||
linkUrls := strings.Split(p.Config.LinkUrls, ",")
|
|
||||||
linkTitles := strings.Split(p.Config.LinkTitles, ",")
|
|
||||||
log.Println("sending message type: " + p.Config.MsgType)
|
|
||||||
switch strings.ToLower(p.Config.MsgType) {
|
switch strings.ToLower(p.Config.MsgType) {
|
||||||
case "markdown":
|
case "markdown":
|
||||||
err := p.WebHook.SendMarkdownMsg(
|
err = newWebHook.SendMarkdownMsg(p.Config.TipsTitle, tpl, p.Config.IsAtALL, mobiles...)
|
||||||
"You have a new message...",
|
|
||||||
p.baseTpl(),
|
|
||||||
p.Config.IsAtALL,
|
|
||||||
mobiles...
|
|
||||||
)
|
|
||||||
if nil != err {
|
|
||||||
log.Println(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case "text":
|
case "text":
|
||||||
err := p.WebHook.SendTextMsg(p.baseTpl(), p.Config.IsAtALL, mobiles...)
|
err = newWebHook.SendTextMsg(tpl, p.Config.IsAtALL, mobiles...)
|
||||||
if nil != err {
|
|
||||||
log.Println(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case "actioncard":
|
|
||||||
err := p.WebHook.SendActionCardMsg(
|
|
||||||
"A actionCard title",
|
|
||||||
p.baseTpl(),
|
|
||||||
linkUrls,
|
|
||||||
linkTitles,
|
|
||||||
p.Config.HideAvatar,
|
|
||||||
p.Config.BtnOrientation,
|
|
||||||
)
|
|
||||||
if nil != err {
|
|
||||||
log.Println(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case "link":
|
case "link":
|
||||||
err := p.WebHook.SendLinkMsg(p.Build.Status, p.baseTpl(), p.Commit.Authors.Avatar, p.Build.Link)
|
err = newWebHook.SendLinkMsg(p.Drone.Build.Status, tpl, p.Drone.Commit.Author.Avatar, p.Drone.Build.Link)
|
||||||
if nil != err {
|
|
||||||
log.Println(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
msg := "not support message type"
|
msg := "not support message type"
|
||||||
log.Println(msg)
|
err = errors.New(msg)
|
||||||
return errors.New(msg)
|
|
||||||
}
|
}
|
||||||
log.Println("send " + p.Config.MsgType + " message success!")
|
|
||||||
return nil
|
if err == nil {
|
||||||
|
log.Println("send message success!")
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Plugin) markdownTpl() string {
|
// fileExists check file is exists
|
||||||
var tpl string
|
func fileExists(filePath string) bool {
|
||||||
|
_, err := os.Stat(filePath)
|
||||||
// title
|
if err != nil {
|
||||||
title := fmt.Sprintf(" %s *Branch Build %s*",
|
if os.IsExist(err) {
|
||||||
strings.Title(p.Commit.Branch),
|
return true
|
||||||
strings.Title(p.Build.Status))
|
}
|
||||||
// with color on title
|
return false
|
||||||
if p.Config.WithColor {
|
|
||||||
title = fmt.Sprintf("<font color=%s>%s</font>", p.getColor(), title)
|
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
tpl = fmt.Sprintf("# %s \n", title)
|
|
||||||
|
|
||||||
// with pic
|
|
||||||
if p.Config.WithPic {
|
|
||||||
tpl += fmt.Sprintf("\n\n",
|
|
||||||
p.Build.Status,
|
|
||||||
p.getPicUrl())
|
|
||||||
}
|
|
||||||
|
|
||||||
// commit message
|
|
||||||
commitMsg := fmt.Sprintf("%s", p.Commit.Message)
|
|
||||||
if p.Config.WithColor {
|
|
||||||
commitMsg = fmt.Sprintf("<font color=%s>%s</font>", p.getColor(), commitMsg)
|
|
||||||
}
|
|
||||||
tpl += commitMsg + "\n\n"
|
|
||||||
|
|
||||||
// sha info
|
|
||||||
commitSha := p.Commit.Sha
|
|
||||||
if p.Config.LinkSha {
|
|
||||||
commitSha = fmt.Sprintf("[Click To %s Commit Detail Page](%s)", commitSha[:6], p.Commit.Link)
|
|
||||||
}
|
|
||||||
tpl += commitSha + "\n\n"
|
|
||||||
|
|
||||||
// author info
|
|
||||||
authorInfo := fmt.Sprintf("`%s(%s)`", p.Commit.Authors.Name, p.Commit.Authors.Email)
|
|
||||||
tpl += authorInfo + "\n\n"
|
|
||||||
|
|
||||||
// build detail link
|
|
||||||
buildDetail := fmt.Sprintf("[Click To The Build Detail Page %s](%s)",
|
|
||||||
p.getEmoticon(),
|
|
||||||
p.Build.Link)
|
|
||||||
tpl += buildDetail
|
|
||||||
return tpl
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Plugin) baseTpl() string {
|
// getTpl get tpl from local file or remote file
|
||||||
tpl := ""
|
func (p *Plugin) getTpl() (tpl string, err error) {
|
||||||
switch strings.ToLower(p.Config.MsgType) {
|
//var tpl string
|
||||||
case "markdown":
|
tplDir := "/app/drone/dingtalk/message/tpls"
|
||||||
tpl = p.markdownTpl()
|
if "" == p.Custom.Tpl {
|
||||||
case "text":
|
p.Custom.Tpl = fmt.Sprintf("%s/%s.tpl", tplDir, strings.ToLower(p.Config.MsgType))
|
||||||
tpl = fmt.Sprintf(`[%s] %s
|
}
|
||||||
%s (%s)
|
|
||||||
@%s
|
|
||||||
%s (%s)
|
|
||||||
`,
|
|
||||||
p.Build.Status,
|
|
||||||
strings.TrimSpace(p.Commit.Message),
|
|
||||||
p.Repo.FullName,
|
|
||||||
p.Commit.Branch,
|
|
||||||
p.Commit.Sha,
|
|
||||||
p.Commit.Authors.Name,
|
|
||||||
p.Commit.Authors.Email)
|
|
||||||
case "link":
|
|
||||||
tpl = fmt.Sprintf(`%s(%s) @%s %s(%s)`,
|
|
||||||
p.Repo.FullName,
|
|
||||||
p.Commit.Branch,
|
|
||||||
p.Commit.Sha[:6],
|
|
||||||
p.Commit.Authors.Name,
|
|
||||||
p.Commit.Authors.Email)
|
|
||||||
case "actionCard":
|
|
||||||
// coming soon
|
|
||||||
|
|
||||||
|
u, err := url.Parse(p.Custom.Tpl)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Scheme != "" {
|
||||||
|
resp, err := http.Get(p.Custom.Tpl)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// check response
|
||||||
|
if u.Path != resp.Request.URL.Path {
|
||||||
|
return "", errors.New("cannot get tpl from url")
|
||||||
|
}
|
||||||
|
|
||||||
|
// defer close
|
||||||
|
defer func() {
|
||||||
|
_ = resp.Body.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
tpl = string(body)
|
||||||
|
} else {
|
||||||
|
if !fileExists(p.Custom.Tpl) {
|
||||||
|
// it must be a tpl stream
|
||||||
|
return p.Custom.Tpl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tplStr, err := ioutil.ReadFile(p.Custom.Tpl)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
tpl = string(tplStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tpl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// fillTpl fill the tpl by valid keyword
|
||||||
|
func (p *Plugin) fillTpl(tpl string) string {
|
||||||
|
envs := p.getEnvs()
|
||||||
|
// replace regex
|
||||||
|
reg := regexp.MustCompile(`\[([^\[\]]*)]`)
|
||||||
|
match := reg.FindAllStringSubmatch(tpl, -1)
|
||||||
|
for _, m := range match {
|
||||||
|
// from environment
|
||||||
|
if envStr := os.Getenv(m[1]); envStr != "" {
|
||||||
|
tpl = strings.ReplaceAll(tpl, m[0], envStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the keyword is legal
|
||||||
|
if _, ok := envs[m[1]]; ok {
|
||||||
|
// replace keyword
|
||||||
|
tpl = strings.ReplaceAll(tpl, m[0], envs[m[1]].(string))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return tpl
|
return tpl
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// getEnvs get available envs
|
||||||
get emoticon
|
func (p *Plugin) getEnvs() map[string]interface{} {
|
||||||
*/
|
var envs map[string]interface{}
|
||||||
|
envs = make(map[string]interface{})
|
||||||
|
envs["TPL_REPO_FULL_NAME"] = p.Drone.Repo.FullName
|
||||||
|
if p.Tpl.Repo.FullName != "" {
|
||||||
|
envs["TPL_REPO_FULL_NAME"] = p.Tpl.Repo.FullName
|
||||||
|
}
|
||||||
|
envs["TPL_REPO_SHORT_NAME"] = p.Drone.Repo.ShortName
|
||||||
|
if p.Tpl.Repo.ShortName != "" {
|
||||||
|
envs["TPL_REPO_SHORT_NAME"] = p.Tpl.Repo.ShortName
|
||||||
|
}
|
||||||
|
envs["TPL_REPO_GROUP_NAME"] = p.Drone.Repo.GroupName
|
||||||
|
envs["TPL_REPO_OWNER_NAME"] = p.Drone.Repo.OwnerName
|
||||||
|
envs["TPL_REPO_REMOTE_URL"] = p.Drone.Repo.RemoteURL
|
||||||
|
|
||||||
|
envs["TPL_BUILD_STATUS"] = p.getStatus()
|
||||||
|
envs["TPL_BUILD_LINK"] = p.Drone.Build.Link
|
||||||
|
envs["TPL_BUILD_EVENT"] = p.Drone.Build.Event
|
||||||
|
|
||||||
|
var consuming uint64
|
||||||
|
// custom consuming env
|
||||||
|
if p.Custom.Consuming.FinishedEnv != "" && p.Custom.Consuming.StartedEnv != "" {
|
||||||
|
finishedAt, _ := strconv.ParseUint(os.Getenv(p.Custom.Consuming.FinishedEnv), 10, 64)
|
||||||
|
startedAt, _ := strconv.ParseUint(os.Getenv(p.Custom.Consuming.StartedEnv), 10, 64)
|
||||||
|
consuming = finishedAt - startedAt
|
||||||
|
} else {
|
||||||
|
consuming = p.Drone.Build.FinishedAt - p.Drone.Build.StartAt
|
||||||
|
if consuming == 0 {
|
||||||
|
consuming = p.Drone.Stage.FinishedAt - p.Drone.Stage.StartedAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
envs["TPL_BUILD_CONSUMING"] = fmt.Sprintf("%v", consuming)
|
||||||
|
|
||||||
|
envs["TPL_COMMIT_SHA"] = p.Drone.Commit.Sha
|
||||||
|
envs["TPL_COMMIT_REF"] = p.Drone.Commit.Ref
|
||||||
|
envs["TPL_COMMIT_LINK"] = p.Drone.Commit.Link
|
||||||
|
envs["TPL_COMMIT_MSG"] = p.Drone.Commit.Message
|
||||||
|
envs["TPL_COMMIT_BRANCH"] = p.Drone.Commit.Branch
|
||||||
|
if p.Tpl.Commit.Branch != "" {
|
||||||
|
envs["TPL_COMMIT_BRANCH"] = p.Tpl.Commit.Branch
|
||||||
|
}
|
||||||
|
|
||||||
|
envs["TPL_AUTHOR_NAME"] = p.Drone.Commit.Author.Name
|
||||||
|
envs["TPL_AUTHOR_USERNAME"] = p.Drone.Commit.Author.Username
|
||||||
|
envs["TPL_AUTHOR_EMAIL"] = p.Drone.Commit.Author.Email
|
||||||
|
envs["TPL_AUTHOR_AVATAR"] = p.Drone.Commit.Author.Avatar
|
||||||
|
|
||||||
|
envs["TPL_STATUS_PIC"] = p.getPicURL()
|
||||||
|
envs["TPL_STATUS_COLOR"] = p.getColor()
|
||||||
|
envs["TPL_STATUS_EMOTICON"] = p.getEmoticon()
|
||||||
|
|
||||||
|
return envs
|
||||||
|
}
|
||||||
|
|
||||||
|
// getMessage get message tpl
|
||||||
|
func (p *Plugin) getMessage() (tpl string, err error) {
|
||||||
|
tpl, err = p.getTpl()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return p.fillTpl(tpl), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getStatus
|
||||||
|
func (p *Plugin) getStatus() string {
|
||||||
|
if p.Drone.Build.Status == "success" {
|
||||||
|
if p.Tpl.Build.Status.Success != "" {
|
||||||
|
return p.Tpl.Build.Status.Success
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.Drone.Build.Status
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Tpl.Build.Status.Failure != "" {
|
||||||
|
return p.Tpl.Build.Status.Failure
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.Drone.Build.Status
|
||||||
|
}
|
||||||
|
|
||||||
|
// get emoticon
|
||||||
func (p *Plugin) getEmoticon() string {
|
func (p *Plugin) getEmoticon() string {
|
||||||
emoticons := make(map[string]string)
|
emoticons := make(map[string]string)
|
||||||
emoticons["success"] = ":)"
|
emoticons["success"] = ":)"
|
||||||
emoticons["failure"] = ":("
|
emoticons["failure"] = ":("
|
||||||
|
|
||||||
emoticon, ok := emoticons[p.Build.Status]
|
emoticon, ok := emoticons[p.Drone.Build.Status]
|
||||||
if ok {
|
if ok {
|
||||||
return emoticon
|
return emoticon
|
||||||
}
|
}
|
||||||
@@ -262,47 +387,50 @@ func (p *Plugin) getEmoticon() string {
|
|||||||
return ":("
|
return ":("
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// get picture url
|
||||||
get picture url
|
func (p *Plugin) getPicURL() string {
|
||||||
*/
|
|
||||||
func (p *Plugin) getPicUrl() string {
|
|
||||||
pics := make(map[string]string)
|
pics := make(map[string]string)
|
||||||
// success picture url
|
// success picture url
|
||||||
pics["success"] = "https://ws4.sinaimg.cn/large/006tNc79gy1fz05g5a7utj30he0bfjry.jpg"
|
pics["success"] = "https://wx1.sinaimg.cn/large/006tNc79gy1fz05g5a7utj30he0bfjry.jpg"
|
||||||
if p.Config.SuccessPicUrl != "" {
|
if p.Custom.Pic.SuccessPicURL != "" {
|
||||||
pics["success"] = p.Config.SuccessPicUrl
|
pics["success"] = p.Custom.Pic.SuccessPicURL
|
||||||
}
|
}
|
||||||
// failure picture url
|
// failure picture url
|
||||||
pics["failure"] = "https://ws1.sinaimg.cn/large/006tNc79gy1fz0b4fghpnj30hd0bdmxn.jpg"
|
pics["failure"] = "https://wx1.sinaimg.cn/large/006tNc79gy1fz0b4fghpnj30hd0bdmxn.jpg"
|
||||||
if p.Config.FailurePicUrl != "" {
|
if p.Custom.Pic.FailurePicURL != "" {
|
||||||
pics["failure"] = p.Config.FailurePicUrl
|
pics["failure"] = p.Custom.Pic.FailurePicURL
|
||||||
}
|
}
|
||||||
|
|
||||||
url, ok := pics[p.Build.Status]
|
picURL, ok := pics[p.Drone.Build.Status]
|
||||||
if ok {
|
if ok {
|
||||||
return url
|
return picURL
|
||||||
}
|
}
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// get color for message title
|
||||||
get color for message title
|
|
||||||
*/
|
|
||||||
func (p *Plugin) getColor() string {
|
func (p *Plugin) getColor() string {
|
||||||
colors := make(map[string]string)
|
colors := make(map[string]string)
|
||||||
// success color
|
// success color
|
||||||
colors["success"] = "#008000"
|
colors["success"] = "#008000"
|
||||||
if p.Config.SuccessColor != "" {
|
if p.Custom.Color.SuccessColor != "" {
|
||||||
colors["success"] = "#" + p.Config.SuccessColor
|
if p.Custom.Color.SuccessColor[0] != '#' {
|
||||||
}
|
p.Custom.Color.SuccessColor = "#" + p.Custom.Color.SuccessColor
|
||||||
// failure color
|
}
|
||||||
colors["failure"] = "#FF0000"
|
colors["success"] = p.Custom.Color.SuccessColor
|
||||||
if p.Config.FailureColor != "" {
|
|
||||||
colors["failure"] = "#" + p.Config.FailureColor
|
|
||||||
}
|
}
|
||||||
|
|
||||||
color, ok := colors[p.Build.Status]
|
// failure color
|
||||||
|
colors["failure"] = "#FF0000"
|
||||||
|
if p.Custom.Color.FailureColor != "" {
|
||||||
|
if p.Custom.Color.FailureColor[0] != '#' {
|
||||||
|
p.Custom.Color.FailureColor = "#" + p.Custom.Color.FailureColor
|
||||||
|
}
|
||||||
|
colors["failure"] = p.Custom.Color.FailureColor
|
||||||
|
}
|
||||||
|
|
||||||
|
color, ok := colors[p.Drone.Build.Status]
|
||||||
if ok {
|
if ok {
|
||||||
return color
|
return color
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPlugin(t *testing.T) {
|
||||||
|
p := Plugin{}
|
||||||
|
err := p.Exec()
|
||||||
|
if nil == err {
|
||||||
|
t.Error("access token empty error should be catch!")
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Config.AccessToken = "example-access-token"
|
||||||
|
p.Custom.Tpl = "tpls/markdown.tpl"
|
||||||
|
err = p.Exec()
|
||||||
|
if nil == err {
|
||||||
|
t.Error("not support message type error should be catch!")
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Config.MsgType = "link"
|
||||||
|
err = p.Exec()
|
||||||
|
if nil == err {
|
||||||
|
t.Error("access token invalid error should be catch!")
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Custom.Tpl = "https://aaa.com"
|
||||||
|
p.Config.MsgType = "text"
|
||||||
|
err = p.Exec()
|
||||||
|
if nil == err {
|
||||||
|
t.Error("access token invalid error should be catch!")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
p.Custom.Tpl = ""
|
||||||
|
p.Config.MsgType = "link"
|
||||||
|
err = p.Exec()
|
||||||
|
if nil == err {
|
||||||
|
t.Error("access token invalid error should be catch!")
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Custom.Color.FailureColor = "#555555"
|
||||||
|
p.Custom.Color.SuccessColor = "#222222"
|
||||||
|
p.Custom.Pic.FailurePicURL = "https://www.baidu.com"
|
||||||
|
p.Custom.Pic.SuccessPicURL = "https://www.baidu.com"
|
||||||
|
p.Config.MsgType = "markdown"
|
||||||
|
p.Custom.Tpl = "tpls/markdown.tpl"
|
||||||
|
err = p.Exec()
|
||||||
|
if nil == err {
|
||||||
|
t.Error("access token invalid error should be catch!")
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Custom.Tpl = "https://gist.githubusercontent.com/lddsb/87065e73678dcf56cd222a3c2f1f32b0/raw/fce9fb28b2c8c768eb93df5598beee8c98cba610/md.tpl"
|
||||||
|
p.Drone.Build.Status = "failure"
|
||||||
|
err = p.Exec()
|
||||||
|
if nil == err {
|
||||||
|
t.Error("access token invalid error should be catch!")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Log("plugin testing finished")
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
### **[CI_PROJECT_TITLE]**'s **[CI_COMMIT_BRANCH]** build **[TPL_BUILD_STATUS]**
|
||||||
|
|
||||||
|
Message: [CI_COMMIT_MESSAGE]
|
||||||
|
|
||||||
|
Detail: [[CI_COMMIT_SHA]]([CI_PROJECT_URL]/commit/[CI_COMMIT_SHA])
|
||||||
|
|
||||||
|
Author: [[GITLAB_USER_NAME]([GITLAB_USER_EMAIL])](mailto:[GITLAB_USER_EMAIL])
|
||||||
|
|
||||||
|
[Click To The Build Detail Page [TPL_STATUS_EMOTICON]]([CI_PIPELINE_URL])
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
### [TPL_REPO_SHORT_NAME] build [TPL_BUILD_STATUS] (`takes [TPL_BUILD_CONSUMING]s`)
|
||||||
|
|
||||||
|
Message: [TPL_COMMIT_MSG]
|
||||||
|
|
||||||
|
Detail: [[TPL_COMMIT_SHA]]([TPL_COMMIT_LINK])
|
||||||
|
|
||||||
|
Author: [[TPL_AUTHOR_NAME]([TPL_AUTHOR_EMAIL])](mailto:[TPL_AUTHOR_EMAIL])
|
||||||
|
|
||||||
|
[Click To The Build Detail Page [TPL_STATUS_EMOTICON]]([TPL_BUILD_LINK])
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
### [TPL_REPO_SHORT_NAME] build [TPL_BUILD_STATUS] (`takes [TPL_BUILD_CONSUMING]s`)
|
||||||
|
|
||||||
|
@mobile1 @mobile2
|
||||||
|
|
||||||
|
Message: [TPL_COMMIT_MSG]
|
||||||
|
|
||||||
|
Detail: [[TPL_COMMIT_SHA]]([TPL_COMMIT_LINK])
|
||||||
|
|
||||||
|
Author: [[TPL_AUTHOR_NAME]([TPL_AUTHOR_EMAIL])](mailto:[TPL_AUTHOR_EMAIL])
|
||||||
|
|
||||||
|
[Click To The Build Detail Page [TPL_STATUS_EMOTICON]]([TPL_BUILD_LINK])
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
[TPL_REPO_NAME] build [TPL_BUILD_STATUS] (takes [TPL_BUILD_CONSUMING]s)
|
||||||
|
[TPL_COMMIT_MSG]
|
||||||
|
[TPL_COMMIT_SHA] ([TPL_COMMIT_LINK])
|
||||||
|
[TPL_AUTHOR_NAME] ([TPL_AUTHOR_EMAIL])
|
||||||
|
Click To The Build Detail Page [TPL_STATUS_EMOTICON] ([TPL_BUILD_LINK])
|
||||||
-1
@@ -1 +0,0 @@
|
|||||||
.DS_Store
|
|
||||||
-8
@@ -1,8 +0,0 @@
|
|||||||
language: go
|
|
||||||
|
|
||||||
go:
|
|
||||||
- 1.x
|
|
||||||
|
|
||||||
os:
|
|
||||||
- linux
|
|
||||||
- osx
|
|
||||||
-23
@@ -1,23 +0,0 @@
|
|||||||
Copyright (c) 2013 John Barton
|
|
||||||
|
|
||||||
MIT License
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
a copy of this software and associated documentation files (the
|
|
||||||
"Software"), to deal in the Software without restriction, including
|
|
||||||
without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be
|
|
||||||
included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
-163
@@ -1,163 +0,0 @@
|
|||||||
# GoDotEnv [](https://travis-ci.org/joho/godotenv) [](https://ci.appveyor.com/project/joho/godotenv) [](https://goreportcard.com/report/github.com/joho/godotenv)
|
|
||||||
|
|
||||||
A Go (golang) port of the Ruby dotenv project (which loads env vars from a .env file)
|
|
||||||
|
|
||||||
From the original Library:
|
|
||||||
|
|
||||||
> Storing configuration in the environment is one of the tenets of a twelve-factor app. Anything that is likely to change between deployment environments–such as resource handles for databases or credentials for external services–should be extracted from the code into environment variables.
|
|
||||||
>
|
|
||||||
> But it is not always practical to set environment variables on development machines or continuous integration servers where multiple projects are run. Dotenv load variables from a .env file into ENV when the environment is bootstrapped.
|
|
||||||
|
|
||||||
It can be used as a library (for loading in env for your own daemons etc) or as a bin command.
|
|
||||||
|
|
||||||
There is test coverage and CI for both linuxish and windows environments, but I make no guarantees about the bin version working on windows.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
As a library
|
|
||||||
|
|
||||||
```shell
|
|
||||||
go get github.com/joho/godotenv
|
|
||||||
```
|
|
||||||
|
|
||||||
or if you want to use it as a bin command
|
|
||||||
```shell
|
|
||||||
go get github.com/joho/godotenv/cmd/godotenv
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
Add your application configuration to your `.env` file in the root of your project:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
S3_BUCKET=YOURS3BUCKET
|
|
||||||
SECRET_KEY=YOURSECRETKEYGOESHERE
|
|
||||||
```
|
|
||||||
|
|
||||||
Then in your Go app you can do something like
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/joho/godotenv"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
err := godotenv.Load()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error loading .env file")
|
|
||||||
}
|
|
||||||
|
|
||||||
s3Bucket := os.Getenv("S3_BUCKET")
|
|
||||||
secretKey := os.Getenv("SECRET_KEY")
|
|
||||||
|
|
||||||
// now do something with s3 or whatever
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
If you're even lazier than that, you can just take advantage of the autoload package which will read in `.env` on import
|
|
||||||
|
|
||||||
```go
|
|
||||||
import _ "github.com/joho/godotenv/autoload"
|
|
||||||
```
|
|
||||||
|
|
||||||
While `.env` in the project root is the default, you don't have to be constrained, both examples below are 100% legit
|
|
||||||
|
|
||||||
```go
|
|
||||||
_ = godotenv.Load("somerandomfile")
|
|
||||||
_ = godotenv.Load("filenumberone.env", "filenumbertwo.env")
|
|
||||||
```
|
|
||||||
|
|
||||||
If you want to be really fancy with your env file you can do comments and exports (below is a valid env file)
|
|
||||||
|
|
||||||
```shell
|
|
||||||
# I am a comment and that is OK
|
|
||||||
SOME_VAR=someval
|
|
||||||
FOO=BAR # comments at line end are OK too
|
|
||||||
export BAR=BAZ
|
|
||||||
```
|
|
||||||
|
|
||||||
Or finally you can do YAML(ish) style
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
FOO: bar
|
|
||||||
BAR: baz
|
|
||||||
```
|
|
||||||
|
|
||||||
as a final aside, if you don't want godotenv munging your env you can just get a map back instead
|
|
||||||
|
|
||||||
```go
|
|
||||||
var myEnv map[string]string
|
|
||||||
myEnv, err := godotenv.Read()
|
|
||||||
|
|
||||||
s3Bucket := myEnv["S3_BUCKET"]
|
|
||||||
```
|
|
||||||
|
|
||||||
... or from an `io.Reader` instead of a local file
|
|
||||||
|
|
||||||
```go
|
|
||||||
reader := getRemoteFile()
|
|
||||||
myEnv, err := godotenv.Parse(reader)
|
|
||||||
```
|
|
||||||
|
|
||||||
... or from a `string` if you so desire
|
|
||||||
|
|
||||||
```go
|
|
||||||
content := getRemoteFileContent()
|
|
||||||
myEnv, err := godotenv.Unmarshal(content)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Command Mode
|
|
||||||
|
|
||||||
Assuming you've installed the command as above and you've got `$GOPATH/bin` in your `$PATH`
|
|
||||||
|
|
||||||
```
|
|
||||||
godotenv -f /some/path/to/.env some_command with some args
|
|
||||||
```
|
|
||||||
|
|
||||||
If you don't specify `-f` it will fall back on the default of loading `.env` in `PWD`
|
|
||||||
|
|
||||||
### Writing Env Files
|
|
||||||
|
|
||||||
Godotenv can also write a map representing the environment to a correctly-formatted and escaped file
|
|
||||||
|
|
||||||
```go
|
|
||||||
env, err := godotenv.Unmarshal("KEY=value")
|
|
||||||
err := godotenv.Write(env, "./.env")
|
|
||||||
```
|
|
||||||
|
|
||||||
... or to a string
|
|
||||||
|
|
||||||
```go
|
|
||||||
env, err := godotenv.Unmarshal("KEY=value")
|
|
||||||
content, err := godotenv.Marshal(env)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
Contributions are most welcome! The parser itself is pretty stupidly naive and I wouldn't be surprised if it breaks with edge cases.
|
|
||||||
|
|
||||||
*code changes without tests will not be accepted*
|
|
||||||
|
|
||||||
1. Fork it
|
|
||||||
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
||||||
3. Commit your changes (`git commit -am 'Added some feature'`)
|
|
||||||
4. Push to the branch (`git push origin my-new-feature`)
|
|
||||||
5. Create new Pull Request
|
|
||||||
|
|
||||||
## Releases
|
|
||||||
|
|
||||||
Releases should follow [Semver](http://semver.org/) though the first couple of releases are `v1` and `v1.1`.
|
|
||||||
|
|
||||||
Use [annotated tags for all releases](https://github.com/joho/godotenv/issues/30). Example `git tag -a v1.2.1`
|
|
||||||
|
|
||||||
## CI
|
|
||||||
|
|
||||||
Linux: [](https://travis-ci.org/joho/godotenv) Windows: [](https://ci.appveyor.com/project/joho/godotenv)
|
|
||||||
|
|
||||||
## Who?
|
|
||||||
|
|
||||||
The original library [dotenv](https://github.com/bkeepers/dotenv) was written by [Brandon Keepers](http://opensoul.org/), and this port was done by [John Barton](https://johnbarton.co/) based off the tests/fixtures in the original library.
|
|
||||||
-15
@@ -1,15 +0,0 @@
|
|||||||
package autoload
|
|
||||||
|
|
||||||
/*
|
|
||||||
You can just read the .env file on import just by doing
|
|
||||||
|
|
||||||
import _ "github.com/joho/godotenv/autoload"
|
|
||||||
|
|
||||||
And bob's your mother's brother
|
|
||||||
*/
|
|
||||||
|
|
||||||
import "github.com/joho/godotenv"
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
godotenv.Load()
|
|
||||||
}
|
|
||||||
-346
@@ -1,346 +0,0 @@
|
|||||||
// Package godotenv is a go port of the ruby dotenv library (https://github.com/bkeepers/dotenv)
|
|
||||||
//
|
|
||||||
// Examples/readme can be found on the github page at https://github.com/joho/godotenv
|
|
||||||
//
|
|
||||||
// The TL;DR is that you make a .env file that looks something like
|
|
||||||
//
|
|
||||||
// SOME_ENV_VAR=somevalue
|
|
||||||
//
|
|
||||||
// and then in your go code you can call
|
|
||||||
//
|
|
||||||
// godotenv.Load()
|
|
||||||
//
|
|
||||||
// and all the env vars declared in .env will be available through os.Getenv("SOME_ENV_VAR")
|
|
||||||
package godotenv
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"regexp"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
const doubleQuoteSpecialChars = "\\\n\r\"!$`"
|
|
||||||
|
|
||||||
// Load will read your env file(s) and load them into ENV for this process.
|
|
||||||
//
|
|
||||||
// Call this function as close as possible to the start of your program (ideally in main)
|
|
||||||
//
|
|
||||||
// If you call Load without any args it will default to loading .env in the current path
|
|
||||||
//
|
|
||||||
// You can otherwise tell it which files to load (there can be more than one) like
|
|
||||||
//
|
|
||||||
// godotenv.Load("fileone", "filetwo")
|
|
||||||
//
|
|
||||||
// It's important to note that it WILL NOT OVERRIDE an env variable that already exists - consider the .env file to set dev vars or sensible defaults
|
|
||||||
func Load(filenames ...string) (err error) {
|
|
||||||
filenames = filenamesOrDefault(filenames)
|
|
||||||
|
|
||||||
for _, filename := range filenames {
|
|
||||||
err = loadFile(filename, false)
|
|
||||||
if err != nil {
|
|
||||||
return // return early on a spazout
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Overload will read your env file(s) and load them into ENV for this process.
|
|
||||||
//
|
|
||||||
// Call this function as close as possible to the start of your program (ideally in main)
|
|
||||||
//
|
|
||||||
// If you call Overload without any args it will default to loading .env in the current path
|
|
||||||
//
|
|
||||||
// You can otherwise tell it which files to load (there can be more than one) like
|
|
||||||
//
|
|
||||||
// godotenv.Overload("fileone", "filetwo")
|
|
||||||
//
|
|
||||||
// It's important to note this WILL OVERRIDE an env variable that already exists - consider the .env file to forcefilly set all vars.
|
|
||||||
func Overload(filenames ...string) (err error) {
|
|
||||||
filenames = filenamesOrDefault(filenames)
|
|
||||||
|
|
||||||
for _, filename := range filenames {
|
|
||||||
err = loadFile(filename, true)
|
|
||||||
if err != nil {
|
|
||||||
return // return early on a spazout
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read all env (with same file loading semantics as Load) but return values as
|
|
||||||
// a map rather than automatically writing values into env
|
|
||||||
func Read(filenames ...string) (envMap map[string]string, err error) {
|
|
||||||
filenames = filenamesOrDefault(filenames)
|
|
||||||
envMap = make(map[string]string)
|
|
||||||
|
|
||||||
for _, filename := range filenames {
|
|
||||||
individualEnvMap, individualErr := readFile(filename)
|
|
||||||
|
|
||||||
if individualErr != nil {
|
|
||||||
err = individualErr
|
|
||||||
return // return early on a spazout
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, value := range individualEnvMap {
|
|
||||||
envMap[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse reads an env file from io.Reader, returning a map of keys and values.
|
|
||||||
func Parse(r io.Reader) (envMap map[string]string, err error) {
|
|
||||||
envMap = make(map[string]string)
|
|
||||||
|
|
||||||
var lines []string
|
|
||||||
scanner := bufio.NewScanner(r)
|
|
||||||
for scanner.Scan() {
|
|
||||||
lines = append(lines, scanner.Text())
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = scanner.Err(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, fullLine := range lines {
|
|
||||||
if !isIgnoredLine(fullLine) {
|
|
||||||
var key, value string
|
|
||||||
key, value, err = parseLine(fullLine, envMap)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
envMap[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
//Unmarshal reads an env file from a string, returning a map of keys and values.
|
|
||||||
func Unmarshal(str string) (envMap map[string]string, err error) {
|
|
||||||
return Parse(strings.NewReader(str))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exec loads env vars from the specified filenames (empty map falls back to default)
|
|
||||||
// then executes the cmd specified.
|
|
||||||
//
|
|
||||||
// Simply hooks up os.Stdin/err/out to the command and calls Run()
|
|
||||||
//
|
|
||||||
// If you want more fine grained control over your command it's recommended
|
|
||||||
// that you use `Load()` or `Read()` and the `os/exec` package yourself.
|
|
||||||
func Exec(filenames []string, cmd string, cmdArgs []string) error {
|
|
||||||
Load(filenames...)
|
|
||||||
|
|
||||||
command := exec.Command(cmd, cmdArgs...)
|
|
||||||
command.Stdin = os.Stdin
|
|
||||||
command.Stdout = os.Stdout
|
|
||||||
command.Stderr = os.Stderr
|
|
||||||
return command.Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write serializes the given environment and writes it to a file
|
|
||||||
func Write(envMap map[string]string, filename string) error {
|
|
||||||
content, error := Marshal(envMap)
|
|
||||||
if error != nil {
|
|
||||||
return error
|
|
||||||
}
|
|
||||||
file, error := os.Create(filename)
|
|
||||||
if error != nil {
|
|
||||||
return error
|
|
||||||
}
|
|
||||||
_, err := file.WriteString(content)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marshal outputs the given environment as a dotenv-formatted environment file.
|
|
||||||
// Each line is in the format: KEY="VALUE" where VALUE is backslash-escaped.
|
|
||||||
func Marshal(envMap map[string]string) (string, error) {
|
|
||||||
lines := make([]string, 0, len(envMap))
|
|
||||||
for k, v := range envMap {
|
|
||||||
lines = append(lines, fmt.Sprintf(`%s="%s"`, k, doubleQuoteEscape(v)))
|
|
||||||
}
|
|
||||||
sort.Strings(lines)
|
|
||||||
return strings.Join(lines, "\n"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func filenamesOrDefault(filenames []string) []string {
|
|
||||||
if len(filenames) == 0 {
|
|
||||||
return []string{".env"}
|
|
||||||
}
|
|
||||||
return filenames
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadFile(filename string, overload bool) error {
|
|
||||||
envMap, err := readFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
currentEnv := map[string]bool{}
|
|
||||||
rawEnv := os.Environ()
|
|
||||||
for _, rawEnvLine := range rawEnv {
|
|
||||||
key := strings.Split(rawEnvLine, "=")[0]
|
|
||||||
currentEnv[key] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, value := range envMap {
|
|
||||||
if !currentEnv[key] || overload {
|
|
||||||
os.Setenv(key, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readFile(filename string) (envMap map[string]string, err error) {
|
|
||||||
file, err := os.Open(filename)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
return Parse(file)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseLine(line string, envMap map[string]string) (key string, value string, err error) {
|
|
||||||
if len(line) == 0 {
|
|
||||||
err = errors.New("zero length string")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ditch the comments (but keep quoted hashes)
|
|
||||||
if strings.Contains(line, "#") {
|
|
||||||
segmentsBetweenHashes := strings.Split(line, "#")
|
|
||||||
quotesAreOpen := false
|
|
||||||
var segmentsToKeep []string
|
|
||||||
for _, segment := range segmentsBetweenHashes {
|
|
||||||
if strings.Count(segment, "\"") == 1 || strings.Count(segment, "'") == 1 {
|
|
||||||
if quotesAreOpen {
|
|
||||||
quotesAreOpen = false
|
|
||||||
segmentsToKeep = append(segmentsToKeep, segment)
|
|
||||||
} else {
|
|
||||||
quotesAreOpen = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(segmentsToKeep) == 0 || quotesAreOpen {
|
|
||||||
segmentsToKeep = append(segmentsToKeep, segment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
line = strings.Join(segmentsToKeep, "#")
|
|
||||||
}
|
|
||||||
|
|
||||||
firstEquals := strings.Index(line, "=")
|
|
||||||
firstColon := strings.Index(line, ":")
|
|
||||||
splitString := strings.SplitN(line, "=", 2)
|
|
||||||
if firstColon != -1 && (firstColon < firstEquals || firstEquals == -1) {
|
|
||||||
//this is a yaml-style line
|
|
||||||
splitString = strings.SplitN(line, ":", 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(splitString) != 2 {
|
|
||||||
err = errors.New("Can't separate key from value")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the key
|
|
||||||
key = splitString[0]
|
|
||||||
if strings.HasPrefix(key, "export") {
|
|
||||||
key = strings.TrimPrefix(key, "export")
|
|
||||||
}
|
|
||||||
key = strings.Trim(key, " ")
|
|
||||||
|
|
||||||
// Parse the value
|
|
||||||
value = parseValue(splitString[1], envMap)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseValue(value string, envMap map[string]string) string {
|
|
||||||
|
|
||||||
// trim
|
|
||||||
value = strings.Trim(value, " ")
|
|
||||||
|
|
||||||
// check if we've got quoted values or possible escapes
|
|
||||||
if len(value) > 1 {
|
|
||||||
rs := regexp.MustCompile(`\A'(.*)'\z`)
|
|
||||||
singleQuotes := rs.FindStringSubmatch(value)
|
|
||||||
|
|
||||||
rd := regexp.MustCompile(`\A"(.*)"\z`)
|
|
||||||
doubleQuotes := rd.FindStringSubmatch(value)
|
|
||||||
|
|
||||||
if singleQuotes != nil || doubleQuotes != nil {
|
|
||||||
// pull the quotes off the edges
|
|
||||||
value = value[1 : len(value)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
if doubleQuotes != nil {
|
|
||||||
// expand newlines
|
|
||||||
escapeRegex := regexp.MustCompile(`\\.`)
|
|
||||||
value = escapeRegex.ReplaceAllStringFunc(value, func(match string) string {
|
|
||||||
c := strings.TrimPrefix(match, `\`)
|
|
||||||
switch c {
|
|
||||||
case "n":
|
|
||||||
return "\n"
|
|
||||||
case "r":
|
|
||||||
return "\r"
|
|
||||||
default:
|
|
||||||
return match
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// unescape characters
|
|
||||||
e := regexp.MustCompile(`\\([^$])`)
|
|
||||||
value = e.ReplaceAllString(value, "$1")
|
|
||||||
}
|
|
||||||
|
|
||||||
if singleQuotes == nil {
|
|
||||||
value = expandVariables(value, envMap)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
func expandVariables(v string, m map[string]string) string {
|
|
||||||
r := regexp.MustCompile(`(\\)?(\$)(\()?\{?([A-Z0-9_]+)?\}?`)
|
|
||||||
|
|
||||||
return r.ReplaceAllStringFunc(v, func(s string) string {
|
|
||||||
submatch := r.FindStringSubmatch(s)
|
|
||||||
|
|
||||||
if submatch == nil {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
if submatch[1] == "\\" || submatch[2] == "(" {
|
|
||||||
return submatch[0][1:]
|
|
||||||
} else if submatch[4] != "" {
|
|
||||||
return m[submatch[4]]
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func isIgnoredLine(line string) bool {
|
|
||||||
trimmedLine := strings.Trim(line, " \n\t")
|
|
||||||
return len(trimmedLine) == 0 || strings.HasPrefix(trimmedLine, "#")
|
|
||||||
}
|
|
||||||
|
|
||||||
func doubleQuoteEscape(line string) string {
|
|
||||||
for _, c := range doubleQuoteSpecialChars {
|
|
||||||
toReplace := "\\" + string(c)
|
|
||||||
if c == '\n' {
|
|
||||||
toReplace = `\n`
|
|
||||||
}
|
|
||||||
if c == '\r' {
|
|
||||||
toReplace = `\r`
|
|
||||||
}
|
|
||||||
line = strings.Replace(line, string(c), toReplace, -1)
|
|
||||||
}
|
|
||||||
return line
|
|
||||||
}
|
|
||||||
-2
@@ -1,2 +0,0 @@
|
|||||||
[flake8]
|
|
||||||
max-line-length = 120
|
|
||||||
Generated
-2
@@ -1,2 +0,0 @@
|
|||||||
*.coverprofile
|
|
||||||
node_modules/
|
|
||||||
-27
@@ -1,27 +0,0 @@
|
|||||||
language: go
|
|
||||||
sudo: false
|
|
||||||
dist: trusty
|
|
||||||
osx_image: xcode8.3
|
|
||||||
go: 1.8.x
|
|
||||||
|
|
||||||
os:
|
|
||||||
- linux
|
|
||||||
- osx
|
|
||||||
|
|
||||||
cache:
|
|
||||||
directories:
|
|
||||||
- node_modules
|
|
||||||
|
|
||||||
before_script:
|
|
||||||
- go get github.com/urfave/gfmrun/... || true
|
|
||||||
- go get golang.org/x/tools/cmd/goimports
|
|
||||||
- if [ ! -f node_modules/.bin/markdown-toc ] ; then
|
|
||||||
npm install markdown-toc ;
|
|
||||||
fi
|
|
||||||
|
|
||||||
script:
|
|
||||||
- ./runtests gen
|
|
||||||
- ./runtests vet
|
|
||||||
- ./runtests test
|
|
||||||
- ./runtests gfmrun
|
|
||||||
- ./runtests toc
|
|
||||||
-435
@@ -1,435 +0,0 @@
|
|||||||
# Change Log
|
|
||||||
|
|
||||||
**ATTN**: This project uses [semantic versioning](http://semver.org/).
|
|
||||||
|
|
||||||
## [Unreleased]
|
|
||||||
|
|
||||||
## 1.20.0 - 2017-08-10
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
* `HandleExitCoder` is now correctly iterates over all errors in
|
|
||||||
a `MultiError`. The exit code is the exit code of the last error or `1` if
|
|
||||||
there are no `ExitCoder`s in the `MultiError`.
|
|
||||||
* Fixed YAML file loading on Windows (previously would fail validate the file path)
|
|
||||||
* Subcommand `Usage`, `Description`, `ArgsUsage`, `OnUsageError` correctly
|
|
||||||
propogated
|
|
||||||
* `ErrWriter` is now passed downwards through command structure to avoid the
|
|
||||||
need to redefine it
|
|
||||||
* Pass `Command` context into `OnUsageError` rather than parent context so that
|
|
||||||
all fields are avaiable
|
|
||||||
* Errors occuring in `Before` funcs are no longer double printed
|
|
||||||
* Use `UsageText` in the help templates for commands and subcommands if
|
|
||||||
defined; otherwise build the usage as before (was previously ignoring this
|
|
||||||
field)
|
|
||||||
* `IsSet` and `GlobalIsSet` now correctly return whether a flag is set if
|
|
||||||
a program calls `Set` or `GlobalSet` directly after flag parsing (would
|
|
||||||
previously only return `true` if the flag was set during parsing)
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
* No longer exit the program on command/subcommand error if the error raised is
|
|
||||||
not an `OsExiter`. This exiting behavior was introduced in 1.19.0, but was
|
|
||||||
determined to be a regression in functionality. See [the
|
|
||||||
PR](https://github.com/urfave/cli/pull/595) for discussion.
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
* `CommandsByName` type was added to make it easy to sort `Command`s by name,
|
|
||||||
alphabetically
|
|
||||||
* `altsrc` now handles loading of string and int arrays from TOML
|
|
||||||
* Support for definition of custom help templates for `App` via
|
|
||||||
`CustomAppHelpTemplate`
|
|
||||||
* Support for arbitrary key/value fields on `App` to be used with
|
|
||||||
`CustomAppHelpTemplate` via `ExtraInfo`
|
|
||||||
* `HelpFlag`, `VersionFlag`, and `BashCompletionFlag` changed to explictly be
|
|
||||||
`cli.Flag`s allowing for the use of custom flags satisfying the `cli.Flag`
|
|
||||||
interface to be used.
|
|
||||||
|
|
||||||
|
|
||||||
## [1.19.1] - 2016-11-21
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Fixes regression introduced in 1.19.0 where using an `ActionFunc` as
|
|
||||||
the `Action` for a command would cause it to error rather than calling the
|
|
||||||
function. Should not have a affected declarative cases using `func(c
|
|
||||||
*cli.Context) err)`.
|
|
||||||
- Shell completion now handles the case where the user specifies
|
|
||||||
`--generate-bash-completion` immediately after a flag that takes an argument.
|
|
||||||
Previously it call the application with `--generate-bash-completion` as the
|
|
||||||
flag value.
|
|
||||||
|
|
||||||
## [1.19.0] - 2016-11-19
|
|
||||||
### Added
|
|
||||||
- `FlagsByName` was added to make it easy to sort flags (e.g. `sort.Sort(cli.FlagsByName(app.Flags))`)
|
|
||||||
- A `Description` field was added to `App` for a more detailed description of
|
|
||||||
the application (similar to the existing `Description` field on `Command`)
|
|
||||||
- Flag type code generation via `go generate`
|
|
||||||
- Write to stderr and exit 1 if action returns non-nil error
|
|
||||||
- Added support for TOML to the `altsrc` loader
|
|
||||||
- `SkipArgReorder` was added to allow users to skip the argument reordering.
|
|
||||||
This is useful if you want to consider all "flags" after an argument as
|
|
||||||
arguments rather than flags (the default behavior of the stdlib `flag`
|
|
||||||
library). This is backported functionality from the [removal of the flag
|
|
||||||
reordering](https://github.com/urfave/cli/pull/398) in the unreleased version
|
|
||||||
2
|
|
||||||
- For formatted errors (those implementing `ErrorFormatter`), the errors will
|
|
||||||
be formatted during output. Compatible with `pkg/errors`.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Raise minimum tested/supported Go version to 1.2+
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Consider empty environment variables as set (previously environment variables
|
|
||||||
with the equivalent of `""` would be skipped rather than their value used).
|
|
||||||
- Return an error if the value in a given environment variable cannot be parsed
|
|
||||||
as the flag type. Previously these errors were silently swallowed.
|
|
||||||
- Print full error when an invalid flag is specified (which includes the invalid flag)
|
|
||||||
- `App.Writer` defaults to `stdout` when `nil`
|
|
||||||
- If no action is specified on a command or app, the help is now printed instead of `panic`ing
|
|
||||||
- `App.Metadata` is initialized automatically now (previously was `nil` unless initialized)
|
|
||||||
- Correctly show help message if `-h` is provided to a subcommand
|
|
||||||
- `context.(Global)IsSet` now respects environment variables. Previously it
|
|
||||||
would return `false` if a flag was specified in the environment rather than
|
|
||||||
as an argument
|
|
||||||
- Removed deprecation warnings to STDERR to avoid them leaking to the end-user
|
|
||||||
- `altsrc`s import paths were updated to use `gopkg.in/urfave/cli.v1`. This
|
|
||||||
fixes issues that occurred when `gopkg.in/urfave/cli.v1` was imported as well
|
|
||||||
as `altsrc` where Go would complain that the types didn't match
|
|
||||||
|
|
||||||
## [1.18.1] - 2016-08-28
|
|
||||||
### Fixed
|
|
||||||
- Removed deprecation warnings to STDERR to avoid them leaking to the end-user (backported)
|
|
||||||
|
|
||||||
## [1.18.0] - 2016-06-27
|
|
||||||
### Added
|
|
||||||
- `./runtests` test runner with coverage tracking by default
|
|
||||||
- testing on OS X
|
|
||||||
- testing on Windows
|
|
||||||
- `UintFlag`, `Uint64Flag`, and `Int64Flag` types and supporting code
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Use spaces for alignment in help/usage output instead of tabs, making the
|
|
||||||
output alignment consistent regardless of tab width
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Printing of command aliases in help text
|
|
||||||
- Printing of visible flags for both struct and struct pointer flags
|
|
||||||
- Display the `help` subcommand when using `CommandCategories`
|
|
||||||
- No longer swallows `panic`s that occur within the `Action`s themselves when
|
|
||||||
detecting the signature of the `Action` field
|
|
||||||
|
|
||||||
## [1.17.1] - 2016-08-28
|
|
||||||
### Fixed
|
|
||||||
- Removed deprecation warnings to STDERR to avoid them leaking to the end-user
|
|
||||||
|
|
||||||
## [1.17.0] - 2016-05-09
|
|
||||||
### Added
|
|
||||||
- Pluggable flag-level help text rendering via `cli.DefaultFlagStringFunc`
|
|
||||||
- `context.GlobalBoolT` was added as an analogue to `context.GlobalBool`
|
|
||||||
- Support for hiding commands by setting `Hidden: true` -- this will hide the
|
|
||||||
commands in help output
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- `Float64Flag`, `IntFlag`, and `DurationFlag` default values are no longer
|
|
||||||
quoted in help text output.
|
|
||||||
- All flag types now include `(default: {value})` strings following usage when a
|
|
||||||
default value can be (reasonably) detected.
|
|
||||||
- `IntSliceFlag` and `StringSliceFlag` usage strings are now more consistent
|
|
||||||
with non-slice flag types
|
|
||||||
- Apps now exit with a code of 3 if an unknown subcommand is specified
|
|
||||||
(previously they printed "No help topic for...", but still exited 0. This
|
|
||||||
makes it easier to script around apps built using `cli` since they can trust
|
|
||||||
that a 0 exit code indicated a successful execution.
|
|
||||||
- cleanups based on [Go Report Card
|
|
||||||
feedback](https://goreportcard.com/report/github.com/urfave/cli)
|
|
||||||
|
|
||||||
## [1.16.1] - 2016-08-28
|
|
||||||
### Fixed
|
|
||||||
- Removed deprecation warnings to STDERR to avoid them leaking to the end-user
|
|
||||||
|
|
||||||
## [1.16.0] - 2016-05-02
|
|
||||||
### Added
|
|
||||||
- `Hidden` field on all flag struct types to omit from generated help text
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- `BashCompletionFlag` (`--enable-bash-completion`) is now omitted from
|
|
||||||
generated help text via the `Hidden` field
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- handling of error values in `HandleAction` and `HandleExitCoder`
|
|
||||||
|
|
||||||
## [1.15.0] - 2016-04-30
|
|
||||||
### Added
|
|
||||||
- This file!
|
|
||||||
- Support for placeholders in flag usage strings
|
|
||||||
- `App.Metadata` map for arbitrary data/state management
|
|
||||||
- `Set` and `GlobalSet` methods on `*cli.Context` for altering values after
|
|
||||||
parsing.
|
|
||||||
- Support for nested lookup of dot-delimited keys in structures loaded from
|
|
||||||
YAML.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- The `App.Action` and `Command.Action` now prefer a return signature of
|
|
||||||
`func(*cli.Context) error`, as defined by `cli.ActionFunc`. If a non-nil
|
|
||||||
`error` is returned, there may be two outcomes:
|
|
||||||
- If the error fulfills `cli.ExitCoder`, then `os.Exit` will be called
|
|
||||||
automatically
|
|
||||||
- Else the error is bubbled up and returned from `App.Run`
|
|
||||||
- Specifying an `Action` with the legacy return signature of
|
|
||||||
`func(*cli.Context)` will produce a deprecation message to stderr
|
|
||||||
- Specifying an `Action` that is not a `func` type will produce a non-zero exit
|
|
||||||
from `App.Run`
|
|
||||||
- Specifying an `Action` func that has an invalid (input) signature will
|
|
||||||
produce a non-zero exit from `App.Run`
|
|
||||||
|
|
||||||
### Deprecated
|
|
||||||
- <a name="deprecated-cli-app-runandexitonerror"></a>
|
|
||||||
`cli.App.RunAndExitOnError`, which should now be done by returning an error
|
|
||||||
that fulfills `cli.ExitCoder` to `cli.App.Run`.
|
|
||||||
- <a name="deprecated-cli-app-action-signature"></a> the legacy signature for
|
|
||||||
`cli.App.Action` of `func(*cli.Context)`, which should now have a return
|
|
||||||
signature of `func(*cli.Context) error`, as defined by `cli.ActionFunc`.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Added missing `*cli.Context.GlobalFloat64` method
|
|
||||||
|
|
||||||
## [1.14.0] - 2016-04-03 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- Codebeat badge
|
|
||||||
- Support for categorization via `CategorizedHelp` and `Categories` on app.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Use `filepath.Base` instead of `path.Base` in `Name` and `HelpName`.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Ensure version is not shown in help text when `HideVersion` set.
|
|
||||||
|
|
||||||
## [1.13.0] - 2016-03-06 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- YAML file input support.
|
|
||||||
- `NArg` method on context.
|
|
||||||
|
|
||||||
## [1.12.0] - 2016-02-17 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- Custom usage error handling.
|
|
||||||
- Custom text support in `USAGE` section of help output.
|
|
||||||
- Improved help messages for empty strings.
|
|
||||||
- AppVeyor CI configuration.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Removed `panic` from default help printer func.
|
|
||||||
- De-duping and optimizations.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Correctly handle `Before`/`After` at command level when no subcommands.
|
|
||||||
- Case of literal `-` argument causing flag reordering.
|
|
||||||
- Environment variable hints on Windows.
|
|
||||||
- Docs updates.
|
|
||||||
|
|
||||||
## [1.11.1] - 2015-12-21 (backfilled 2016-04-25)
|
|
||||||
### Changed
|
|
||||||
- Use `path.Base` in `Name` and `HelpName`
|
|
||||||
- Export `GetName` on flag types.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Flag parsing when skipping is enabled.
|
|
||||||
- Test output cleanup.
|
|
||||||
- Move completion check to account for empty input case.
|
|
||||||
|
|
||||||
## [1.11.0] - 2015-11-15 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- Destination scan support for flags.
|
|
||||||
- Testing against `tip` in Travis CI config.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Go version in Travis CI config.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Removed redundant tests.
|
|
||||||
- Use correct example naming in tests.
|
|
||||||
|
|
||||||
## [1.10.2] - 2015-10-29 (backfilled 2016-04-25)
|
|
||||||
### Fixed
|
|
||||||
- Remove unused var in bash completion.
|
|
||||||
|
|
||||||
## [1.10.1] - 2015-10-21 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- Coverage and reference logos in README.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Use specified values in help and version parsing.
|
|
||||||
- Only display app version and help message once.
|
|
||||||
|
|
||||||
## [1.10.0] - 2015-10-06 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- More tests for existing functionality.
|
|
||||||
- `ArgsUsage` at app and command level for help text flexibility.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Honor `HideHelp` and `HideVersion` in `App.Run`.
|
|
||||||
- Remove juvenile word from README.
|
|
||||||
|
|
||||||
## [1.9.0] - 2015-09-08 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- `FullName` on command with accompanying help output update.
|
|
||||||
- Set default `$PROG` in bash completion.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Docs formatting.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Removed self-referential imports in tests.
|
|
||||||
|
|
||||||
## [1.8.0] - 2015-06-30 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- Support for `Copyright` at app level.
|
|
||||||
- `Parent` func at context level to walk up context lineage.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Global flag processing at top level.
|
|
||||||
|
|
||||||
## [1.7.1] - 2015-06-11 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- Aggregate errors from `Before`/`After` funcs.
|
|
||||||
- Doc comments on flag structs.
|
|
||||||
- Include non-global flags when checking version and help.
|
|
||||||
- Travis CI config updates.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Ensure slice type flags have non-nil values.
|
|
||||||
- Collect global flags from the full command hierarchy.
|
|
||||||
- Docs prose.
|
|
||||||
|
|
||||||
## [1.7.0] - 2015-05-03 (backfilled 2016-04-25)
|
|
||||||
### Changed
|
|
||||||
- `HelpPrinter` signature includes output writer.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Specify go 1.1+ in docs.
|
|
||||||
- Set `Writer` when running command as app.
|
|
||||||
|
|
||||||
## [1.6.0] - 2015-03-23 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- Multiple author support.
|
|
||||||
- `NumFlags` at context level.
|
|
||||||
- `Aliases` at command level.
|
|
||||||
|
|
||||||
### Deprecated
|
|
||||||
- `ShortName` at command level.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Subcommand help output.
|
|
||||||
- Backward compatible support for deprecated `Author` and `Email` fields.
|
|
||||||
- Docs regarding `Names`/`Aliases`.
|
|
||||||
|
|
||||||
## [1.5.0] - 2015-02-20 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- `After` hook func support at app and command level.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Use parsed context when running command as subcommand.
|
|
||||||
- Docs prose.
|
|
||||||
|
|
||||||
## [1.4.1] - 2015-01-09 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- Support for hiding `-h / --help` flags, but not `help` subcommand.
|
|
||||||
- Stop flag parsing after `--`.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Help text for generic flags to specify single value.
|
|
||||||
- Use double quotes in output for defaults.
|
|
||||||
- Use `ParseInt` instead of `ParseUint` for int environment var values.
|
|
||||||
- Use `0` as base when parsing int environment var values.
|
|
||||||
|
|
||||||
## [1.4.0] - 2014-12-12 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- Support for environment variable lookup "cascade".
|
|
||||||
- Support for `Stdout` on app for output redirection.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Print command help instead of app help in `ShowCommandHelp`.
|
|
||||||
|
|
||||||
## [1.3.1] - 2014-11-13 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- Docs and example code updates.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Default `-v / --version` flag made optional.
|
|
||||||
|
|
||||||
## [1.3.0] - 2014-08-10 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- `FlagNames` at context level.
|
|
||||||
- Exposed `VersionPrinter` var for more control over version output.
|
|
||||||
- Zsh completion hook.
|
|
||||||
- `AUTHOR` section in default app help template.
|
|
||||||
- Contribution guidelines.
|
|
||||||
- `DurationFlag` type.
|
|
||||||
|
|
||||||
## [1.2.0] - 2014-08-02
|
|
||||||
### Added
|
|
||||||
- Support for environment variable defaults on flags plus tests.
|
|
||||||
|
|
||||||
## [1.1.0] - 2014-07-15
|
|
||||||
### Added
|
|
||||||
- Bash completion.
|
|
||||||
- Optional hiding of built-in help command.
|
|
||||||
- Optional skipping of flag parsing at command level.
|
|
||||||
- `Author`, `Email`, and `Compiled` metadata on app.
|
|
||||||
- `Before` hook func support at app and command level.
|
|
||||||
- `CommandNotFound` func support at app level.
|
|
||||||
- Command reference available on context.
|
|
||||||
- `GenericFlag` type.
|
|
||||||
- `Float64Flag` type.
|
|
||||||
- `BoolTFlag` type.
|
|
||||||
- `IsSet` flag helper on context.
|
|
||||||
- More flag lookup funcs at context level.
|
|
||||||
- More tests & docs.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Help template updates to account for presence/absence of flags.
|
|
||||||
- Separated subcommand help template.
|
|
||||||
- Exposed `HelpPrinter` var for more control over help output.
|
|
||||||
|
|
||||||
## [1.0.0] - 2013-11-01
|
|
||||||
### Added
|
|
||||||
- `help` flag in default app flag set and each command flag set.
|
|
||||||
- Custom handling of argument parsing errors.
|
|
||||||
- Command lookup by name at app level.
|
|
||||||
- `StringSliceFlag` type and supporting `StringSlice` type.
|
|
||||||
- `IntSliceFlag` type and supporting `IntSlice` type.
|
|
||||||
- Slice type flag lookups by name at context level.
|
|
||||||
- Export of app and command help functions.
|
|
||||||
- More tests & docs.
|
|
||||||
|
|
||||||
## 0.1.0 - 2013-07-22
|
|
||||||
### Added
|
|
||||||
- Initial implementation.
|
|
||||||
|
|
||||||
[Unreleased]: https://github.com/urfave/cli/compare/v1.18.0...HEAD
|
|
||||||
[1.18.0]: https://github.com/urfave/cli/compare/v1.17.0...v1.18.0
|
|
||||||
[1.17.0]: https://github.com/urfave/cli/compare/v1.16.0...v1.17.0
|
|
||||||
[1.16.0]: https://github.com/urfave/cli/compare/v1.15.0...v1.16.0
|
|
||||||
[1.15.0]: https://github.com/urfave/cli/compare/v1.14.0...v1.15.0
|
|
||||||
[1.14.0]: https://github.com/urfave/cli/compare/v1.13.0...v1.14.0
|
|
||||||
[1.13.0]: https://github.com/urfave/cli/compare/v1.12.0...v1.13.0
|
|
||||||
[1.12.0]: https://github.com/urfave/cli/compare/v1.11.1...v1.12.0
|
|
||||||
[1.11.1]: https://github.com/urfave/cli/compare/v1.11.0...v1.11.1
|
|
||||||
[1.11.0]: https://github.com/urfave/cli/compare/v1.10.2...v1.11.0
|
|
||||||
[1.10.2]: https://github.com/urfave/cli/compare/v1.10.1...v1.10.2
|
|
||||||
[1.10.1]: https://github.com/urfave/cli/compare/v1.10.0...v1.10.1
|
|
||||||
[1.10.0]: https://github.com/urfave/cli/compare/v1.9.0...v1.10.0
|
|
||||||
[1.9.0]: https://github.com/urfave/cli/compare/v1.8.0...v1.9.0
|
|
||||||
[1.8.0]: https://github.com/urfave/cli/compare/v1.7.1...v1.8.0
|
|
||||||
[1.7.1]: https://github.com/urfave/cli/compare/v1.7.0...v1.7.1
|
|
||||||
[1.7.0]: https://github.com/urfave/cli/compare/v1.6.0...v1.7.0
|
|
||||||
[1.6.0]: https://github.com/urfave/cli/compare/v1.5.0...v1.6.0
|
|
||||||
[1.5.0]: https://github.com/urfave/cli/compare/v1.4.1...v1.5.0
|
|
||||||
[1.4.1]: https://github.com/urfave/cli/compare/v1.4.0...v1.4.1
|
|
||||||
[1.4.0]: https://github.com/urfave/cli/compare/v1.3.1...v1.4.0
|
|
||||||
[1.3.1]: https://github.com/urfave/cli/compare/v1.3.0...v1.3.1
|
|
||||||
[1.3.0]: https://github.com/urfave/cli/compare/v1.2.0...v1.3.0
|
|
||||||
[1.2.0]: https://github.com/urfave/cli/compare/v1.1.0...v1.2.0
|
|
||||||
[1.1.0]: https://github.com/urfave/cli/compare/v1.0.0...v1.1.0
|
|
||||||
[1.0.0]: https://github.com/urfave/cli/compare/v0.1.0...v1.0.0
|
|
||||||
-1381
File diff suppressed because it is too large
Load Diff
-497
@@ -1,497 +0,0 @@
|
|||||||
package cli
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"sort"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
changeLogURL = "https://github.com/urfave/cli/blob/master/CHANGELOG.md"
|
|
||||||
appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL)
|
|
||||||
runAndExitOnErrorDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-runandexitonerror", changeLogURL)
|
|
||||||
|
|
||||||
contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you."
|
|
||||||
|
|
||||||
errInvalidActionType = NewExitError("ERROR invalid Action type. "+
|
|
||||||
fmt.Sprintf("Must be `func(*Context`)` or `func(*Context) error). %s", contactSysadmin)+
|
|
||||||
fmt.Sprintf("See %s", appActionDeprecationURL), 2)
|
|
||||||
)
|
|
||||||
|
|
||||||
// App is the main structure of a cli application. It is recommended that
|
|
||||||
// an app be created with the cli.NewApp() function
|
|
||||||
type App struct {
|
|
||||||
// The name of the program. Defaults to path.Base(os.Args[0])
|
|
||||||
Name string
|
|
||||||
// Full name of command for help, defaults to Name
|
|
||||||
HelpName string
|
|
||||||
// Description of the program.
|
|
||||||
Usage string
|
|
||||||
// Text to override the USAGE section of help
|
|
||||||
UsageText string
|
|
||||||
// Description of the program argument format.
|
|
||||||
ArgsUsage string
|
|
||||||
// Version of the program
|
|
||||||
Version string
|
|
||||||
// Description of the program
|
|
||||||
Description string
|
|
||||||
// List of commands to execute
|
|
||||||
Commands []Command
|
|
||||||
// List of flags to parse
|
|
||||||
Flags []Flag
|
|
||||||
// Boolean to enable bash completion commands
|
|
||||||
EnableBashCompletion bool
|
|
||||||
// Boolean to hide built-in help command
|
|
||||||
HideHelp bool
|
|
||||||
// Boolean to hide built-in version flag and the VERSION section of help
|
|
||||||
HideVersion bool
|
|
||||||
// Populate on app startup, only gettable through method Categories()
|
|
||||||
categories CommandCategories
|
|
||||||
// An action to execute when the bash-completion flag is set
|
|
||||||
BashComplete BashCompleteFunc
|
|
||||||
// An action to execute before any subcommands are run, but after the context is ready
|
|
||||||
// If a non-nil error is returned, no subcommands are run
|
|
||||||
Before BeforeFunc
|
|
||||||
// An action to execute after any subcommands are run, but after the subcommand has finished
|
|
||||||
// It is run even if Action() panics
|
|
||||||
After AfterFunc
|
|
||||||
|
|
||||||
// The action to execute when no subcommands are specified
|
|
||||||
// Expects a `cli.ActionFunc` but will accept the *deprecated* signature of `func(*cli.Context) {}`
|
|
||||||
// *Note*: support for the deprecated `Action` signature will be removed in a future version
|
|
||||||
Action interface{}
|
|
||||||
|
|
||||||
// Execute this function if the proper command cannot be found
|
|
||||||
CommandNotFound CommandNotFoundFunc
|
|
||||||
// Execute this function if an usage error occurs
|
|
||||||
OnUsageError OnUsageErrorFunc
|
|
||||||
// Compilation date
|
|
||||||
Compiled time.Time
|
|
||||||
// List of all authors who contributed
|
|
||||||
Authors []Author
|
|
||||||
// Copyright of the binary if any
|
|
||||||
Copyright string
|
|
||||||
// Name of Author (Note: Use App.Authors, this is deprecated)
|
|
||||||
Author string
|
|
||||||
// Email of Author (Note: Use App.Authors, this is deprecated)
|
|
||||||
Email string
|
|
||||||
// Writer writer to write output to
|
|
||||||
Writer io.Writer
|
|
||||||
// ErrWriter writes error output
|
|
||||||
ErrWriter io.Writer
|
|
||||||
// Other custom info
|
|
||||||
Metadata map[string]interface{}
|
|
||||||
// Carries a function which returns app specific info.
|
|
||||||
ExtraInfo func() map[string]string
|
|
||||||
// CustomAppHelpTemplate the text template for app help topic.
|
|
||||||
// cli.go uses text/template to render templates. You can
|
|
||||||
// render custom help text by setting this variable.
|
|
||||||
CustomAppHelpTemplate string
|
|
||||||
|
|
||||||
didSetup bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tries to find out when this binary was compiled.
|
|
||||||
// Returns the current time if it fails to find it.
|
|
||||||
func compileTime() time.Time {
|
|
||||||
info, err := os.Stat(os.Args[0])
|
|
||||||
if err != nil {
|
|
||||||
return time.Now()
|
|
||||||
}
|
|
||||||
return info.ModTime()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewApp creates a new cli Application with some reasonable defaults for Name,
|
|
||||||
// Usage, Version and Action.
|
|
||||||
func NewApp() *App {
|
|
||||||
return &App{
|
|
||||||
Name: filepath.Base(os.Args[0]),
|
|
||||||
HelpName: filepath.Base(os.Args[0]),
|
|
||||||
Usage: "A new cli application",
|
|
||||||
UsageText: "",
|
|
||||||
Version: "0.0.0",
|
|
||||||
BashComplete: DefaultAppComplete,
|
|
||||||
Action: helpCommand.Action,
|
|
||||||
Compiled: compileTime(),
|
|
||||||
Writer: os.Stdout,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup runs initialization code to ensure all data structures are ready for
|
|
||||||
// `Run` or inspection prior to `Run`. It is internally called by `Run`, but
|
|
||||||
// will return early if setup has already happened.
|
|
||||||
func (a *App) Setup() {
|
|
||||||
if a.didSetup {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
a.didSetup = true
|
|
||||||
|
|
||||||
if a.Author != "" || a.Email != "" {
|
|
||||||
a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email})
|
|
||||||
}
|
|
||||||
|
|
||||||
newCmds := []Command{}
|
|
||||||
for _, c := range a.Commands {
|
|
||||||
if c.HelpName == "" {
|
|
||||||
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
|
|
||||||
}
|
|
||||||
newCmds = append(newCmds, c)
|
|
||||||
}
|
|
||||||
a.Commands = newCmds
|
|
||||||
|
|
||||||
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
|
|
||||||
a.Commands = append(a.Commands, helpCommand)
|
|
||||||
if (HelpFlag != BoolFlag{}) {
|
|
||||||
a.appendFlag(HelpFlag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !a.HideVersion {
|
|
||||||
a.appendFlag(VersionFlag)
|
|
||||||
}
|
|
||||||
|
|
||||||
a.categories = CommandCategories{}
|
|
||||||
for _, command := range a.Commands {
|
|
||||||
a.categories = a.categories.AddCommand(command.Category, command)
|
|
||||||
}
|
|
||||||
sort.Sort(a.categories)
|
|
||||||
|
|
||||||
if a.Metadata == nil {
|
|
||||||
a.Metadata = make(map[string]interface{})
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.Writer == nil {
|
|
||||||
a.Writer = os.Stdout
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run is the entry point to the cli app. Parses the arguments slice and routes
|
|
||||||
// to the proper flag/args combination
|
|
||||||
func (a *App) Run(arguments []string) (err error) {
|
|
||||||
a.Setup()
|
|
||||||
|
|
||||||
// handle the completion flag separately from the flagset since
|
|
||||||
// completion could be attempted after a flag, but before its value was put
|
|
||||||
// on the command line. this causes the flagset to interpret the completion
|
|
||||||
// flag name as the value of the flag before it which is undesirable
|
|
||||||
// note that we can only do this because the shell autocomplete function
|
|
||||||
// always appends the completion flag at the end of the command
|
|
||||||
shellComplete, arguments := checkShellCompleteFlag(a, arguments)
|
|
||||||
|
|
||||||
// parse flags
|
|
||||||
set, err := flagSet(a.Name, a.Flags)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
set.SetOutput(ioutil.Discard)
|
|
||||||
err = set.Parse(arguments[1:])
|
|
||||||
nerr := normalizeFlags(a.Flags, set)
|
|
||||||
context := NewContext(a, set, nil)
|
|
||||||
if nerr != nil {
|
|
||||||
fmt.Fprintln(a.Writer, nerr)
|
|
||||||
ShowAppHelp(context)
|
|
||||||
return nerr
|
|
||||||
}
|
|
||||||
context.shellComplete = shellComplete
|
|
||||||
|
|
||||||
if checkCompletions(context) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if a.OnUsageError != nil {
|
|
||||||
err := a.OnUsageError(context, err, false)
|
|
||||||
HandleExitCoder(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
|
|
||||||
ShowAppHelp(context)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !a.HideHelp && checkHelp(context) {
|
|
||||||
ShowAppHelp(context)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !a.HideVersion && checkVersion(context) {
|
|
||||||
ShowVersion(context)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.After != nil {
|
|
||||||
defer func() {
|
|
||||||
if afterErr := a.After(context); afterErr != nil {
|
|
||||||
if err != nil {
|
|
||||||
err = NewMultiError(err, afterErr)
|
|
||||||
} else {
|
|
||||||
err = afterErr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.Before != nil {
|
|
||||||
beforeErr := a.Before(context)
|
|
||||||
if beforeErr != nil {
|
|
||||||
ShowAppHelp(context)
|
|
||||||
HandleExitCoder(beforeErr)
|
|
||||||
err = beforeErr
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
args := context.Args()
|
|
||||||
if args.Present() {
|
|
||||||
name := args.First()
|
|
||||||
c := a.Command(name)
|
|
||||||
if c != nil {
|
|
||||||
return c.Run(context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.Action == nil {
|
|
||||||
a.Action = helpCommand.Action
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run default Action
|
|
||||||
err = HandleAction(a.Action, context)
|
|
||||||
|
|
||||||
HandleExitCoder(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunAndExitOnError calls .Run() and exits non-zero if an error was returned
|
|
||||||
//
|
|
||||||
// Deprecated: instead you should return an error that fulfills cli.ExitCoder
|
|
||||||
// to cli.App.Run. This will cause the application to exit with the given eror
|
|
||||||
// code in the cli.ExitCoder
|
|
||||||
func (a *App) RunAndExitOnError() {
|
|
||||||
if err := a.Run(os.Args); err != nil {
|
|
||||||
fmt.Fprintln(a.errWriter(), err)
|
|
||||||
OsExiter(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunAsSubcommand invokes the subcommand given the context, parses ctx.Args() to
|
|
||||||
// generate command-specific flags
|
|
||||||
func (a *App) RunAsSubcommand(ctx *Context) (err error) {
|
|
||||||
// append help to commands
|
|
||||||
if len(a.Commands) > 0 {
|
|
||||||
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
|
|
||||||
a.Commands = append(a.Commands, helpCommand)
|
|
||||||
if (HelpFlag != BoolFlag{}) {
|
|
||||||
a.appendFlag(HelpFlag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
newCmds := []Command{}
|
|
||||||
for _, c := range a.Commands {
|
|
||||||
if c.HelpName == "" {
|
|
||||||
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
|
|
||||||
}
|
|
||||||
newCmds = append(newCmds, c)
|
|
||||||
}
|
|
||||||
a.Commands = newCmds
|
|
||||||
|
|
||||||
// parse flags
|
|
||||||
set, err := flagSet(a.Name, a.Flags)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
set.SetOutput(ioutil.Discard)
|
|
||||||
err = set.Parse(ctx.Args().Tail())
|
|
||||||
nerr := normalizeFlags(a.Flags, set)
|
|
||||||
context := NewContext(a, set, ctx)
|
|
||||||
|
|
||||||
if nerr != nil {
|
|
||||||
fmt.Fprintln(a.Writer, nerr)
|
|
||||||
fmt.Fprintln(a.Writer)
|
|
||||||
if len(a.Commands) > 0 {
|
|
||||||
ShowSubcommandHelp(context)
|
|
||||||
} else {
|
|
||||||
ShowCommandHelp(ctx, context.Args().First())
|
|
||||||
}
|
|
||||||
return nerr
|
|
||||||
}
|
|
||||||
|
|
||||||
if checkCompletions(context) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if a.OnUsageError != nil {
|
|
||||||
err = a.OnUsageError(context, err, true)
|
|
||||||
HandleExitCoder(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
|
|
||||||
ShowSubcommandHelp(context)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(a.Commands) > 0 {
|
|
||||||
if checkSubcommandHelp(context) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if checkCommandHelp(ctx, context.Args().First()) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.After != nil {
|
|
||||||
defer func() {
|
|
||||||
afterErr := a.After(context)
|
|
||||||
if afterErr != nil {
|
|
||||||
HandleExitCoder(err)
|
|
||||||
if err != nil {
|
|
||||||
err = NewMultiError(err, afterErr)
|
|
||||||
} else {
|
|
||||||
err = afterErr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.Before != nil {
|
|
||||||
beforeErr := a.Before(context)
|
|
||||||
if beforeErr != nil {
|
|
||||||
HandleExitCoder(beforeErr)
|
|
||||||
err = beforeErr
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
args := context.Args()
|
|
||||||
if args.Present() {
|
|
||||||
name := args.First()
|
|
||||||
c := a.Command(name)
|
|
||||||
if c != nil {
|
|
||||||
return c.Run(context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run default Action
|
|
||||||
err = HandleAction(a.Action, context)
|
|
||||||
|
|
||||||
HandleExitCoder(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Command returns the named command on App. Returns nil if the command does not exist
|
|
||||||
func (a *App) Command(name string) *Command {
|
|
||||||
for _, c := range a.Commands {
|
|
||||||
if c.HasName(name) {
|
|
||||||
return &c
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Categories returns a slice containing all the categories with the commands they contain
|
|
||||||
func (a *App) Categories() CommandCategories {
|
|
||||||
return a.categories
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisibleCategories returns a slice of categories and commands that are
|
|
||||||
// Hidden=false
|
|
||||||
func (a *App) VisibleCategories() []*CommandCategory {
|
|
||||||
ret := []*CommandCategory{}
|
|
||||||
for _, category := range a.categories {
|
|
||||||
if visible := func() *CommandCategory {
|
|
||||||
for _, command := range category.Commands {
|
|
||||||
if !command.Hidden {
|
|
||||||
return category
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}(); visible != nil {
|
|
||||||
ret = append(ret, visible)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisibleCommands returns a slice of the Commands with Hidden=false
|
|
||||||
func (a *App) VisibleCommands() []Command {
|
|
||||||
ret := []Command{}
|
|
||||||
for _, command := range a.Commands {
|
|
||||||
if !command.Hidden {
|
|
||||||
ret = append(ret, command)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisibleFlags returns a slice of the Flags with Hidden=false
|
|
||||||
func (a *App) VisibleFlags() []Flag {
|
|
||||||
return visibleFlags(a.Flags)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) hasFlag(flag Flag) bool {
|
|
||||||
for _, f := range a.Flags {
|
|
||||||
if flag == f {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) errWriter() io.Writer {
|
|
||||||
|
|
||||||
// When the app ErrWriter is nil use the package level one.
|
|
||||||
if a.ErrWriter == nil {
|
|
||||||
return ErrWriter
|
|
||||||
}
|
|
||||||
|
|
||||||
return a.ErrWriter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) appendFlag(flag Flag) {
|
|
||||||
if !a.hasFlag(flag) {
|
|
||||||
a.Flags = append(a.Flags, flag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Author represents someone who has contributed to a cli project.
|
|
||||||
type Author struct {
|
|
||||||
Name string // The Authors name
|
|
||||||
Email string // The Authors email
|
|
||||||
}
|
|
||||||
|
|
||||||
// String makes Author comply to the Stringer interface, to allow an easy print in the templating process
|
|
||||||
func (a Author) String() string {
|
|
||||||
e := ""
|
|
||||||
if a.Email != "" {
|
|
||||||
e = " <" + a.Email + ">"
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("%v%v", a.Name, e)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleAction attempts to figure out which Action signature was used. If
|
|
||||||
// it's an ActionFunc or a func with the legacy signature for Action, the func
|
|
||||||
// is run!
|
|
||||||
func HandleAction(action interface{}, context *Context) (err error) {
|
|
||||||
if a, ok := action.(ActionFunc); ok {
|
|
||||||
return a(context)
|
|
||||||
} else if a, ok := action.(func(*Context) error); ok {
|
|
||||||
return a(context)
|
|
||||||
} else if a, ok := action.(func(*Context)); ok { // deprecated function signature
|
|
||||||
a(context)
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
return errInvalidActionType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-26
@@ -1,26 +0,0 @@
|
|||||||
version: "{build}"
|
|
||||||
|
|
||||||
os: Windows Server 2016
|
|
||||||
|
|
||||||
image: Visual Studio 2017
|
|
||||||
|
|
||||||
clone_folder: c:\gopath\src\github.com\urfave\cli
|
|
||||||
|
|
||||||
environment:
|
|
||||||
GOPATH: C:\gopath
|
|
||||||
GOVERSION: 1.8.x
|
|
||||||
PYTHON: C:\Python36-x64
|
|
||||||
PYTHON_VERSION: 3.6.x
|
|
||||||
PYTHON_ARCH: 64
|
|
||||||
|
|
||||||
install:
|
|
||||||
- set PATH=%GOPATH%\bin;C:\go\bin;%PATH%
|
|
||||||
- go version
|
|
||||||
- go env
|
|
||||||
- go get github.com/urfave/gfmrun/...
|
|
||||||
- go get -v -t ./...
|
|
||||||
|
|
||||||
build_script:
|
|
||||||
- python runtests vet
|
|
||||||
- python runtests test
|
|
||||||
- python runtests gfmrun
|
|
||||||
-44
@@ -1,44 +0,0 @@
|
|||||||
package cli
|
|
||||||
|
|
||||||
// CommandCategories is a slice of *CommandCategory.
|
|
||||||
type CommandCategories []*CommandCategory
|
|
||||||
|
|
||||||
// CommandCategory is a category containing commands.
|
|
||||||
type CommandCategory struct {
|
|
||||||
Name string
|
|
||||||
Commands Commands
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CommandCategories) Less(i, j int) bool {
|
|
||||||
return c[i].Name < c[j].Name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CommandCategories) Len() int {
|
|
||||||
return len(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CommandCategories) Swap(i, j int) {
|
|
||||||
c[i], c[j] = c[j], c[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddCommand adds a command to a category.
|
|
||||||
func (c CommandCategories) AddCommand(category string, command Command) CommandCategories {
|
|
||||||
for _, commandCategory := range c {
|
|
||||||
if commandCategory.Name == category {
|
|
||||||
commandCategory.Commands = append(commandCategory.Commands, command)
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return append(c, &CommandCategory{Name: category, Commands: []Command{command}})
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisibleCommands returns a slice of the Commands with Hidden=false
|
|
||||||
func (c *CommandCategory) VisibleCommands() []Command {
|
|
||||||
ret := []Command{}
|
|
||||||
for _, command := range c.Commands {
|
|
||||||
if !command.Hidden {
|
|
||||||
ret = append(ret, command)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
-22
@@ -1,22 +0,0 @@
|
|||||||
// Package cli provides a minimal framework for creating and organizing command line
|
|
||||||
// Go applications. cli is designed to be easy to understand and write, the most simple
|
|
||||||
// cli application can be written as follows:
|
|
||||||
// func main() {
|
|
||||||
// cli.NewApp().Run(os.Args)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Of course this application does not do much, so let's make this an actual application:
|
|
||||||
// func main() {
|
|
||||||
// app := cli.NewApp()
|
|
||||||
// app.Name = "greet"
|
|
||||||
// app.Usage = "say a greeting"
|
|
||||||
// app.Action = func(c *cli.Context) error {
|
|
||||||
// println("Greetings")
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// app.Run(os.Args)
|
|
||||||
// }
|
|
||||||
package cli
|
|
||||||
|
|
||||||
//go:generate python ./generate-flag-types cli -i flag-types.json -o flag_generated.go
|
|
||||||
-304
@@ -1,304 +0,0 @@
|
|||||||
package cli
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Command is a subcommand for a cli.App.
|
|
||||||
type Command struct {
|
|
||||||
// The name of the command
|
|
||||||
Name string
|
|
||||||
// short name of the command. Typically one character (deprecated, use `Aliases`)
|
|
||||||
ShortName string
|
|
||||||
// A list of aliases for the command
|
|
||||||
Aliases []string
|
|
||||||
// A short description of the usage of this command
|
|
||||||
Usage string
|
|
||||||
// Custom text to show on USAGE section of help
|
|
||||||
UsageText string
|
|
||||||
// A longer explanation of how the command works
|
|
||||||
Description string
|
|
||||||
// A short description of the arguments of this command
|
|
||||||
ArgsUsage string
|
|
||||||
// The category the command is part of
|
|
||||||
Category string
|
|
||||||
// The function to call when checking for bash command completions
|
|
||||||
BashComplete BashCompleteFunc
|
|
||||||
// An action to execute before any sub-subcommands are run, but after the context is ready
|
|
||||||
// If a non-nil error is returned, no sub-subcommands are run
|
|
||||||
Before BeforeFunc
|
|
||||||
// An action to execute after any subcommands are run, but after the subcommand has finished
|
|
||||||
// It is run even if Action() panics
|
|
||||||
After AfterFunc
|
|
||||||
// The function to call when this command is invoked
|
|
||||||
Action interface{}
|
|
||||||
// TODO: replace `Action: interface{}` with `Action: ActionFunc` once some kind
|
|
||||||
// of deprecation period has passed, maybe?
|
|
||||||
|
|
||||||
// Execute this function if a usage error occurs.
|
|
||||||
OnUsageError OnUsageErrorFunc
|
|
||||||
// List of child commands
|
|
||||||
Subcommands Commands
|
|
||||||
// List of flags to parse
|
|
||||||
Flags []Flag
|
|
||||||
// Treat all flags as normal arguments if true
|
|
||||||
SkipFlagParsing bool
|
|
||||||
// Skip argument reordering which attempts to move flags before arguments,
|
|
||||||
// but only works if all flags appear after all arguments. This behavior was
|
|
||||||
// removed n version 2 since it only works under specific conditions so we
|
|
||||||
// backport here by exposing it as an option for compatibility.
|
|
||||||
SkipArgReorder bool
|
|
||||||
// Boolean to hide built-in help command
|
|
||||||
HideHelp bool
|
|
||||||
// Boolean to hide this command from help or completion
|
|
||||||
Hidden bool
|
|
||||||
|
|
||||||
// Full name of command for help, defaults to full command name, including parent commands.
|
|
||||||
HelpName string
|
|
||||||
commandNamePath []string
|
|
||||||
|
|
||||||
// CustomHelpTemplate the text template for the command help topic.
|
|
||||||
// cli.go uses text/template to render templates. You can
|
|
||||||
// render custom help text by setting this variable.
|
|
||||||
CustomHelpTemplate string
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommandsByName []Command
|
|
||||||
|
|
||||||
func (c CommandsByName) Len() int {
|
|
||||||
return len(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CommandsByName) Less(i, j int) bool {
|
|
||||||
return c[i].Name < c[j].Name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CommandsByName) Swap(i, j int) {
|
|
||||||
c[i], c[j] = c[j], c[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
// FullName returns the full name of the command.
|
|
||||||
// For subcommands this ensures that parent commands are part of the command path
|
|
||||||
func (c Command) FullName() string {
|
|
||||||
if c.commandNamePath == nil {
|
|
||||||
return c.Name
|
|
||||||
}
|
|
||||||
return strings.Join(c.commandNamePath, " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commands is a slice of Command
|
|
||||||
type Commands []Command
|
|
||||||
|
|
||||||
// Run invokes the command given the context, parses ctx.Args() to generate command-specific flags
|
|
||||||
func (c Command) Run(ctx *Context) (err error) {
|
|
||||||
if len(c.Subcommands) > 0 {
|
|
||||||
return c.startApp(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !c.HideHelp && (HelpFlag != BoolFlag{}) {
|
|
||||||
// append help to flags
|
|
||||||
c.Flags = append(
|
|
||||||
c.Flags,
|
|
||||||
HelpFlag,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
set, err := flagSet(c.Name, c.Flags)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
set.SetOutput(ioutil.Discard)
|
|
||||||
|
|
||||||
if c.SkipFlagParsing {
|
|
||||||
err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...))
|
|
||||||
} else if !c.SkipArgReorder {
|
|
||||||
firstFlagIndex := -1
|
|
||||||
terminatorIndex := -1
|
|
||||||
for index, arg := range ctx.Args() {
|
|
||||||
if arg == "--" {
|
|
||||||
terminatorIndex = index
|
|
||||||
break
|
|
||||||
} else if arg == "-" {
|
|
||||||
// Do nothing. A dash alone is not really a flag.
|
|
||||||
continue
|
|
||||||
} else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 {
|
|
||||||
firstFlagIndex = index
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if firstFlagIndex > -1 {
|
|
||||||
args := ctx.Args()
|
|
||||||
regularArgs := make([]string, len(args[1:firstFlagIndex]))
|
|
||||||
copy(regularArgs, args[1:firstFlagIndex])
|
|
||||||
|
|
||||||
var flagArgs []string
|
|
||||||
if terminatorIndex > -1 {
|
|
||||||
flagArgs = args[firstFlagIndex:terminatorIndex]
|
|
||||||
regularArgs = append(regularArgs, args[terminatorIndex:]...)
|
|
||||||
} else {
|
|
||||||
flagArgs = args[firstFlagIndex:]
|
|
||||||
}
|
|
||||||
|
|
||||||
err = set.Parse(append(flagArgs, regularArgs...))
|
|
||||||
} else {
|
|
||||||
err = set.Parse(ctx.Args().Tail())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err = set.Parse(ctx.Args().Tail())
|
|
||||||
}
|
|
||||||
|
|
||||||
nerr := normalizeFlags(c.Flags, set)
|
|
||||||
if nerr != nil {
|
|
||||||
fmt.Fprintln(ctx.App.Writer, nerr)
|
|
||||||
fmt.Fprintln(ctx.App.Writer)
|
|
||||||
ShowCommandHelp(ctx, c.Name)
|
|
||||||
return nerr
|
|
||||||
}
|
|
||||||
|
|
||||||
context := NewContext(ctx.App, set, ctx)
|
|
||||||
context.Command = c
|
|
||||||
if checkCommandCompletions(context, c.Name) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if c.OnUsageError != nil {
|
|
||||||
err := c.OnUsageError(context, err, false)
|
|
||||||
HandleExitCoder(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error())
|
|
||||||
fmt.Fprintln(context.App.Writer)
|
|
||||||
ShowCommandHelp(context, c.Name)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if checkCommandHelp(context, c.Name) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.After != nil {
|
|
||||||
defer func() {
|
|
||||||
afterErr := c.After(context)
|
|
||||||
if afterErr != nil {
|
|
||||||
HandleExitCoder(err)
|
|
||||||
if err != nil {
|
|
||||||
err = NewMultiError(err, afterErr)
|
|
||||||
} else {
|
|
||||||
err = afterErr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Before != nil {
|
|
||||||
err = c.Before(context)
|
|
||||||
if err != nil {
|
|
||||||
ShowCommandHelp(context, c.Name)
|
|
||||||
HandleExitCoder(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Action == nil {
|
|
||||||
c.Action = helpSubcommand.Action
|
|
||||||
}
|
|
||||||
|
|
||||||
err = HandleAction(c.Action, context)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
HandleExitCoder(err)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Names returns the names including short names and aliases.
|
|
||||||
func (c Command) Names() []string {
|
|
||||||
names := []string{c.Name}
|
|
||||||
|
|
||||||
if c.ShortName != "" {
|
|
||||||
names = append(names, c.ShortName)
|
|
||||||
}
|
|
||||||
|
|
||||||
return append(names, c.Aliases...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasName returns true if Command.Name or Command.ShortName matches given name
|
|
||||||
func (c Command) HasName(name string) bool {
|
|
||||||
for _, n := range c.Names() {
|
|
||||||
if n == name {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Command) startApp(ctx *Context) error {
|
|
||||||
app := NewApp()
|
|
||||||
app.Metadata = ctx.App.Metadata
|
|
||||||
// set the name and usage
|
|
||||||
app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name)
|
|
||||||
if c.HelpName == "" {
|
|
||||||
app.HelpName = c.HelpName
|
|
||||||
} else {
|
|
||||||
app.HelpName = app.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
app.Usage = c.Usage
|
|
||||||
app.Description = c.Description
|
|
||||||
app.ArgsUsage = c.ArgsUsage
|
|
||||||
|
|
||||||
// set CommandNotFound
|
|
||||||
app.CommandNotFound = ctx.App.CommandNotFound
|
|
||||||
app.CustomAppHelpTemplate = c.CustomHelpTemplate
|
|
||||||
|
|
||||||
// set the flags and commands
|
|
||||||
app.Commands = c.Subcommands
|
|
||||||
app.Flags = c.Flags
|
|
||||||
app.HideHelp = c.HideHelp
|
|
||||||
|
|
||||||
app.Version = ctx.App.Version
|
|
||||||
app.HideVersion = ctx.App.HideVersion
|
|
||||||
app.Compiled = ctx.App.Compiled
|
|
||||||
app.Author = ctx.App.Author
|
|
||||||
app.Email = ctx.App.Email
|
|
||||||
app.Writer = ctx.App.Writer
|
|
||||||
app.ErrWriter = ctx.App.ErrWriter
|
|
||||||
|
|
||||||
app.categories = CommandCategories{}
|
|
||||||
for _, command := range c.Subcommands {
|
|
||||||
app.categories = app.categories.AddCommand(command.Category, command)
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(app.categories)
|
|
||||||
|
|
||||||
// bash completion
|
|
||||||
app.EnableBashCompletion = ctx.App.EnableBashCompletion
|
|
||||||
if c.BashComplete != nil {
|
|
||||||
app.BashComplete = c.BashComplete
|
|
||||||
}
|
|
||||||
|
|
||||||
// set the actions
|
|
||||||
app.Before = c.Before
|
|
||||||
app.After = c.After
|
|
||||||
if c.Action != nil {
|
|
||||||
app.Action = c.Action
|
|
||||||
} else {
|
|
||||||
app.Action = helpSubcommand.Action
|
|
||||||
}
|
|
||||||
app.OnUsageError = c.OnUsageError
|
|
||||||
|
|
||||||
for index, cc := range app.Commands {
|
|
||||||
app.Commands[index].commandNamePath = []string{c.Name, cc.Name}
|
|
||||||
}
|
|
||||||
|
|
||||||
return app.RunAsSubcommand(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisibleFlags returns a slice of the Flags with Hidden=false
|
|
||||||
func (c Command) VisibleFlags() []Flag {
|
|
||||||
return visibleFlags(c.Flags)
|
|
||||||
}
|
|
||||||
-278
@@ -1,278 +0,0 @@
|
|||||||
package cli
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"flag"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Context is a type that is passed through to
|
|
||||||
// each Handler action in a cli application. Context
|
|
||||||
// can be used to retrieve context-specific Args and
|
|
||||||
// parsed command-line options.
|
|
||||||
type Context struct {
|
|
||||||
App *App
|
|
||||||
Command Command
|
|
||||||
shellComplete bool
|
|
||||||
flagSet *flag.FlagSet
|
|
||||||
setFlags map[string]bool
|
|
||||||
parentContext *Context
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewContext creates a new context. For use in when invoking an App or Command action.
|
|
||||||
func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context {
|
|
||||||
c := &Context{App: app, flagSet: set, parentContext: parentCtx}
|
|
||||||
|
|
||||||
if parentCtx != nil {
|
|
||||||
c.shellComplete = parentCtx.shellComplete
|
|
||||||
}
|
|
||||||
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// NumFlags returns the number of flags set
|
|
||||||
func (c *Context) NumFlags() int {
|
|
||||||
return c.flagSet.NFlag()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set sets a context flag to a value.
|
|
||||||
func (c *Context) Set(name, value string) error {
|
|
||||||
c.setFlags = nil
|
|
||||||
return c.flagSet.Set(name, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalSet sets a context flag to a value on the global flagset
|
|
||||||
func (c *Context) GlobalSet(name, value string) error {
|
|
||||||
globalContext(c).setFlags = nil
|
|
||||||
return globalContext(c).flagSet.Set(name, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsSet determines if the flag was actually set
|
|
||||||
func (c *Context) IsSet(name string) bool {
|
|
||||||
if c.setFlags == nil {
|
|
||||||
c.setFlags = make(map[string]bool)
|
|
||||||
|
|
||||||
c.flagSet.Visit(func(f *flag.Flag) {
|
|
||||||
c.setFlags[f.Name] = true
|
|
||||||
})
|
|
||||||
|
|
||||||
c.flagSet.VisitAll(func(f *flag.Flag) {
|
|
||||||
if _, ok := c.setFlags[f.Name]; ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.setFlags[f.Name] = false
|
|
||||||
})
|
|
||||||
|
|
||||||
// XXX hack to support IsSet for flags with EnvVar
|
|
||||||
//
|
|
||||||
// There isn't an easy way to do this with the current implementation since
|
|
||||||
// whether a flag was set via an environment variable is very difficult to
|
|
||||||
// determine here. Instead, we intend to introduce a backwards incompatible
|
|
||||||
// change in version 2 to add `IsSet` to the Flag interface to push the
|
|
||||||
// responsibility closer to where the information required to determine
|
|
||||||
// whether a flag is set by non-standard means such as environment
|
|
||||||
// variables is avaliable.
|
|
||||||
//
|
|
||||||
// See https://github.com/urfave/cli/issues/294 for additional discussion
|
|
||||||
flags := c.Command.Flags
|
|
||||||
if c.Command.Name == "" { // cannot == Command{} since it contains slice types
|
|
||||||
if c.App != nil {
|
|
||||||
flags = c.App.Flags
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, f := range flags {
|
|
||||||
eachName(f.GetName(), func(name string) {
|
|
||||||
if isSet, ok := c.setFlags[name]; isSet || !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val := reflect.ValueOf(f)
|
|
||||||
if val.Kind() == reflect.Ptr {
|
|
||||||
val = val.Elem()
|
|
||||||
}
|
|
||||||
|
|
||||||
envVarValue := val.FieldByName("EnvVar")
|
|
||||||
if !envVarValue.IsValid() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(envVarValue.String(), func(envVar string) {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if _, ok := syscall.Getenv(envVar); ok {
|
|
||||||
c.setFlags[name] = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.setFlags[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalIsSet determines if the global flag was actually set
|
|
||||||
func (c *Context) GlobalIsSet(name string) bool {
|
|
||||||
ctx := c
|
|
||||||
if ctx.parentContext != nil {
|
|
||||||
ctx = ctx.parentContext
|
|
||||||
}
|
|
||||||
|
|
||||||
for ; ctx != nil; ctx = ctx.parentContext {
|
|
||||||
if ctx.IsSet(name) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlagNames returns a slice of flag names used in this context.
|
|
||||||
func (c *Context) FlagNames() (names []string) {
|
|
||||||
for _, flag := range c.Command.Flags {
|
|
||||||
name := strings.Split(flag.GetName(), ",")[0]
|
|
||||||
if name == "help" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
names = append(names, name)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalFlagNames returns a slice of global flag names used by the app.
|
|
||||||
func (c *Context) GlobalFlagNames() (names []string) {
|
|
||||||
for _, flag := range c.App.Flags {
|
|
||||||
name := strings.Split(flag.GetName(), ",")[0]
|
|
||||||
if name == "help" || name == "version" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
names = append(names, name)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parent returns the parent context, if any
|
|
||||||
func (c *Context) Parent() *Context {
|
|
||||||
return c.parentContext
|
|
||||||
}
|
|
||||||
|
|
||||||
// value returns the value of the flag coressponding to `name`
|
|
||||||
func (c *Context) value(name string) interface{} {
|
|
||||||
return c.flagSet.Lookup(name).Value.(flag.Getter).Get()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Args contains apps console arguments
|
|
||||||
type Args []string
|
|
||||||
|
|
||||||
// Args returns the command line arguments associated with the context.
|
|
||||||
func (c *Context) Args() Args {
|
|
||||||
args := Args(c.flagSet.Args())
|
|
||||||
return args
|
|
||||||
}
|
|
||||||
|
|
||||||
// NArg returns the number of the command line arguments.
|
|
||||||
func (c *Context) NArg() int {
|
|
||||||
return len(c.Args())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns the nth argument, or else a blank string
|
|
||||||
func (a Args) Get(n int) string {
|
|
||||||
if len(a) > n {
|
|
||||||
return a[n]
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// First returns the first argument, or else a blank string
|
|
||||||
func (a Args) First() string {
|
|
||||||
return a.Get(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tail returns the rest of the arguments (not the first one)
|
|
||||||
// or else an empty string slice
|
|
||||||
func (a Args) Tail() []string {
|
|
||||||
if len(a) >= 2 {
|
|
||||||
return []string(a)[1:]
|
|
||||||
}
|
|
||||||
return []string{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Present checks if there are any arguments present
|
|
||||||
func (a Args) Present() bool {
|
|
||||||
return len(a) != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Swap swaps arguments at the given indexes
|
|
||||||
func (a Args) Swap(from, to int) error {
|
|
||||||
if from >= len(a) || to >= len(a) {
|
|
||||||
return errors.New("index out of range")
|
|
||||||
}
|
|
||||||
a[from], a[to] = a[to], a[from]
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func globalContext(ctx *Context) *Context {
|
|
||||||
if ctx == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
if ctx.parentContext == nil {
|
|
||||||
return ctx
|
|
||||||
}
|
|
||||||
ctx = ctx.parentContext
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupGlobalFlagSet(name string, ctx *Context) *flag.FlagSet {
|
|
||||||
if ctx.parentContext != nil {
|
|
||||||
ctx = ctx.parentContext
|
|
||||||
}
|
|
||||||
for ; ctx != nil; ctx = ctx.parentContext {
|
|
||||||
if f := ctx.flagSet.Lookup(name); f != nil {
|
|
||||||
return ctx.flagSet
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) {
|
|
||||||
switch ff.Value.(type) {
|
|
||||||
case *StringSlice:
|
|
||||||
default:
|
|
||||||
set.Set(name, ff.Value.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalizeFlags(flags []Flag, set *flag.FlagSet) error {
|
|
||||||
visited := make(map[string]bool)
|
|
||||||
set.Visit(func(f *flag.Flag) {
|
|
||||||
visited[f.Name] = true
|
|
||||||
})
|
|
||||||
for _, f := range flags {
|
|
||||||
parts := strings.Split(f.GetName(), ",")
|
|
||||||
if len(parts) == 1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var ff *flag.Flag
|
|
||||||
for _, name := range parts {
|
|
||||||
name = strings.Trim(name, " ")
|
|
||||||
if visited[name] {
|
|
||||||
if ff != nil {
|
|
||||||
return errors.New("Cannot use two forms of the same flag: " + name + " " + ff.Name)
|
|
||||||
}
|
|
||||||
ff = set.Lookup(name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ff == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, name := range parts {
|
|
||||||
name = strings.Trim(name, " ")
|
|
||||||
if !visited[name] {
|
|
||||||
copyFlag(name, ff, set)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
-115
@@ -1,115 +0,0 @@
|
|||||||
package cli
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// OsExiter is the function used when the app exits. If not set defaults to os.Exit.
|
|
||||||
var OsExiter = os.Exit
|
|
||||||
|
|
||||||
// ErrWriter is used to write errors to the user. This can be anything
|
|
||||||
// implementing the io.Writer interface and defaults to os.Stderr.
|
|
||||||
var ErrWriter io.Writer = os.Stderr
|
|
||||||
|
|
||||||
// MultiError is an error that wraps multiple errors.
|
|
||||||
type MultiError struct {
|
|
||||||
Errors []error
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMultiError creates a new MultiError. Pass in one or more errors.
|
|
||||||
func NewMultiError(err ...error) MultiError {
|
|
||||||
return MultiError{Errors: err}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error implements the error interface.
|
|
||||||
func (m MultiError) Error() string {
|
|
||||||
errs := make([]string, len(m.Errors))
|
|
||||||
for i, err := range m.Errors {
|
|
||||||
errs[i] = err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Join(errs, "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
type ErrorFormatter interface {
|
|
||||||
Format(s fmt.State, verb rune)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExitCoder is the interface checked by `App` and `Command` for a custom exit
|
|
||||||
// code
|
|
||||||
type ExitCoder interface {
|
|
||||||
error
|
|
||||||
ExitCode() int
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExitError fulfills both the builtin `error` interface and `ExitCoder`
|
|
||||||
type ExitError struct {
|
|
||||||
exitCode int
|
|
||||||
message interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewExitError makes a new *ExitError
|
|
||||||
func NewExitError(message interface{}, exitCode int) *ExitError {
|
|
||||||
return &ExitError{
|
|
||||||
exitCode: exitCode,
|
|
||||||
message: message,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error returns the string message, fulfilling the interface required by
|
|
||||||
// `error`
|
|
||||||
func (ee *ExitError) Error() string {
|
|
||||||
return fmt.Sprintf("%v", ee.message)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExitCode returns the exit code, fulfilling the interface required by
|
|
||||||
// `ExitCoder`
|
|
||||||
func (ee *ExitError) ExitCode() int {
|
|
||||||
return ee.exitCode
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleExitCoder checks if the error fulfills the ExitCoder interface, and if
|
|
||||||
// so prints the error to stderr (if it is non-empty) and calls OsExiter with the
|
|
||||||
// given exit code. If the given error is a MultiError, then this func is
|
|
||||||
// called on all members of the Errors slice and calls OsExiter with the last exit code.
|
|
||||||
func HandleExitCoder(err error) {
|
|
||||||
if err == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if exitErr, ok := err.(ExitCoder); ok {
|
|
||||||
if err.Error() != "" {
|
|
||||||
if _, ok := exitErr.(ErrorFormatter); ok {
|
|
||||||
fmt.Fprintf(ErrWriter, "%+v\n", err)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintln(ErrWriter, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
OsExiter(exitErr.ExitCode())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if multiErr, ok := err.(MultiError); ok {
|
|
||||||
code := handleMultiError(multiErr)
|
|
||||||
OsExiter(code)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleMultiError(multiErr MultiError) int {
|
|
||||||
code := 1
|
|
||||||
for _, merr := range multiErr.Errors {
|
|
||||||
if multiErr2, ok := merr.(MultiError); ok {
|
|
||||||
code = handleMultiError(multiErr2)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintln(ErrWriter, merr)
|
|
||||||
if exitErr, ok := merr.(ExitCoder); ok {
|
|
||||||
code = exitErr.ExitCode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
-93
@@ -1,93 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"name": "Bool",
|
|
||||||
"type": "bool",
|
|
||||||
"value": false,
|
|
||||||
"context_default": "false",
|
|
||||||
"parser": "strconv.ParseBool(f.Value.String())"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "BoolT",
|
|
||||||
"type": "bool",
|
|
||||||
"value": false,
|
|
||||||
"doctail": " that is true by default",
|
|
||||||
"context_default": "false",
|
|
||||||
"parser": "strconv.ParseBool(f.Value.String())"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Duration",
|
|
||||||
"type": "time.Duration",
|
|
||||||
"doctail": " (see https://golang.org/pkg/time/#ParseDuration)",
|
|
||||||
"context_default": "0",
|
|
||||||
"parser": "time.ParseDuration(f.Value.String())"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Float64",
|
|
||||||
"type": "float64",
|
|
||||||
"context_default": "0",
|
|
||||||
"parser": "strconv.ParseFloat(f.Value.String(), 64)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Generic",
|
|
||||||
"type": "Generic",
|
|
||||||
"dest": false,
|
|
||||||
"context_default": "nil",
|
|
||||||
"context_type": "interface{}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Int64",
|
|
||||||
"type": "int64",
|
|
||||||
"context_default": "0",
|
|
||||||
"parser": "strconv.ParseInt(f.Value.String(), 0, 64)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Int",
|
|
||||||
"type": "int",
|
|
||||||
"context_default": "0",
|
|
||||||
"parser": "strconv.ParseInt(f.Value.String(), 0, 64)",
|
|
||||||
"parser_cast": "int(parsed)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "IntSlice",
|
|
||||||
"type": "*IntSlice",
|
|
||||||
"dest": false,
|
|
||||||
"context_default": "nil",
|
|
||||||
"context_type": "[]int",
|
|
||||||
"parser": "(f.Value.(*IntSlice)).Value(), error(nil)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Int64Slice",
|
|
||||||
"type": "*Int64Slice",
|
|
||||||
"dest": false,
|
|
||||||
"context_default": "nil",
|
|
||||||
"context_type": "[]int64",
|
|
||||||
"parser": "(f.Value.(*Int64Slice)).Value(), error(nil)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "String",
|
|
||||||
"type": "string",
|
|
||||||
"context_default": "\"\"",
|
|
||||||
"parser": "f.Value.String(), error(nil)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "StringSlice",
|
|
||||||
"type": "*StringSlice",
|
|
||||||
"dest": false,
|
|
||||||
"context_default": "nil",
|
|
||||||
"context_type": "[]string",
|
|
||||||
"parser": "(f.Value.(*StringSlice)).Value(), error(nil)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Uint64",
|
|
||||||
"type": "uint64",
|
|
||||||
"context_default": "0",
|
|
||||||
"parser": "strconv.ParseUint(f.Value.String(), 0, 64)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Uint",
|
|
||||||
"type": "uint",
|
|
||||||
"context_default": "0",
|
|
||||||
"parser": "strconv.ParseUint(f.Value.String(), 0, 64)",
|
|
||||||
"parser_cast": "uint(parsed)"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
-799
@@ -1,799 +0,0 @@
|
|||||||
package cli
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const defaultPlaceholder = "value"
|
|
||||||
|
|
||||||
// BashCompletionFlag enables bash-completion for all commands and subcommands
|
|
||||||
var BashCompletionFlag Flag = BoolFlag{
|
|
||||||
Name: "generate-bash-completion",
|
|
||||||
Hidden: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
// VersionFlag prints the version for the application
|
|
||||||
var VersionFlag Flag = BoolFlag{
|
|
||||||
Name: "version, v",
|
|
||||||
Usage: "print the version",
|
|
||||||
}
|
|
||||||
|
|
||||||
// HelpFlag prints the help for all commands and subcommands
|
|
||||||
// Set to the zero value (BoolFlag{}) to disable flag -- keeps subcommand
|
|
||||||
// unless HideHelp is set to true)
|
|
||||||
var HelpFlag Flag = BoolFlag{
|
|
||||||
Name: "help, h",
|
|
||||||
Usage: "show help",
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlagStringer converts a flag definition to a string. This is used by help
|
|
||||||
// to display a flag.
|
|
||||||
var FlagStringer FlagStringFunc = stringifyFlag
|
|
||||||
|
|
||||||
// FlagsByName is a slice of Flag.
|
|
||||||
type FlagsByName []Flag
|
|
||||||
|
|
||||||
func (f FlagsByName) Len() int {
|
|
||||||
return len(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f FlagsByName) Less(i, j int) bool {
|
|
||||||
return f[i].GetName() < f[j].GetName()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f FlagsByName) Swap(i, j int) {
|
|
||||||
f[i], f[j] = f[j], f[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flag is a common interface related to parsing flags in cli.
|
|
||||||
// For more advanced flag parsing techniques, it is recommended that
|
|
||||||
// this interface be implemented.
|
|
||||||
type Flag interface {
|
|
||||||
fmt.Stringer
|
|
||||||
// Apply Flag settings to the given flag set
|
|
||||||
Apply(*flag.FlagSet)
|
|
||||||
GetName() string
|
|
||||||
}
|
|
||||||
|
|
||||||
// errorableFlag is an interface that allows us to return errors during apply
|
|
||||||
// it allows flags defined in this library to return errors in a fashion backwards compatible
|
|
||||||
// TODO remove in v2 and modify the existing Flag interface to return errors
|
|
||||||
type errorableFlag interface {
|
|
||||||
Flag
|
|
||||||
|
|
||||||
ApplyWithError(*flag.FlagSet) error
|
|
||||||
}
|
|
||||||
|
|
||||||
func flagSet(name string, flags []Flag) (*flag.FlagSet, error) {
|
|
||||||
set := flag.NewFlagSet(name, flag.ContinueOnError)
|
|
||||||
|
|
||||||
for _, f := range flags {
|
|
||||||
//TODO remove in v2 when errorableFlag is removed
|
|
||||||
if ef, ok := f.(errorableFlag); ok {
|
|
||||||
if err := ef.ApplyWithError(set); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
f.Apply(set)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return set, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func eachName(longName string, fn func(string)) {
|
|
||||||
parts := strings.Split(longName, ",")
|
|
||||||
for _, name := range parts {
|
|
||||||
name = strings.Trim(name, " ")
|
|
||||||
fn(name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generic is a generic parseable type identified by a specific flag
|
|
||||||
type Generic interface {
|
|
||||||
Set(value string) error
|
|
||||||
String() string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply takes the flagset and calls Set on the generic flag with the value
|
|
||||||
// provided by the user for parsing by the flag
|
|
||||||
// Ignores parsing errors
|
|
||||||
func (f GenericFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError takes the flagset and calls Set on the generic flag with the value
|
|
||||||
// provided by the user for parsing by the flag
|
|
||||||
func (f GenericFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
val := f.Value
|
|
||||||
if f.EnvVar != "" {
|
|
||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
|
||||||
if err := val.Set(envVal); err != nil {
|
|
||||||
return fmt.Errorf("could not parse %s as value for flag %s: %s", envVal, f.Name, err)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
set.Var(f.Value, name, f.Usage)
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// StringSlice is an opaque type for []string to satisfy flag.Value and flag.Getter
|
|
||||||
type StringSlice []string
|
|
||||||
|
|
||||||
// Set appends the string value to the list of values
|
|
||||||
func (f *StringSlice) Set(value string) error {
|
|
||||||
*f = append(*f, value)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value (for usage defaults)
|
|
||||||
func (f *StringSlice) String() string {
|
|
||||||
return fmt.Sprintf("%s", *f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value returns the slice of strings set by this flag
|
|
||||||
func (f *StringSlice) Value() []string {
|
|
||||||
return *f
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns the slice of strings set by this flag
|
|
||||||
func (f *StringSlice) Get() interface{} {
|
|
||||||
return *f
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
|
||||||
// Ignores errors
|
|
||||||
func (f StringSliceFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||||
func (f StringSliceFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
if f.EnvVar != "" {
|
|
||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
|
||||||
newVal := &StringSlice{}
|
|
||||||
for _, s := range strings.Split(envVal, ",") {
|
|
||||||
s = strings.TrimSpace(s)
|
|
||||||
if err := newVal.Set(s); err != nil {
|
|
||||||
return fmt.Errorf("could not parse %s as string value for flag %s: %s", envVal, f.Name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
f.Value = newVal
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
if f.Value == nil {
|
|
||||||
f.Value = &StringSlice{}
|
|
||||||
}
|
|
||||||
set.Var(f.Value, name, f.Usage)
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntSlice is an opaque type for []int to satisfy flag.Value and flag.Getter
|
|
||||||
type IntSlice []int
|
|
||||||
|
|
||||||
// Set parses the value into an integer and appends it to the list of values
|
|
||||||
func (f *IntSlice) Set(value string) error {
|
|
||||||
tmp, err := strconv.Atoi(value)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*f = append(*f, tmp)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value (for usage defaults)
|
|
||||||
func (f *IntSlice) String() string {
|
|
||||||
return fmt.Sprintf("%#v", *f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value returns the slice of ints set by this flag
|
|
||||||
func (f *IntSlice) Value() []int {
|
|
||||||
return *f
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns the slice of ints set by this flag
|
|
||||||
func (f *IntSlice) Get() interface{} {
|
|
||||||
return *f
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
|
||||||
// Ignores errors
|
|
||||||
func (f IntSliceFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||||
func (f IntSliceFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
if f.EnvVar != "" {
|
|
||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
|
||||||
newVal := &IntSlice{}
|
|
||||||
for _, s := range strings.Split(envVal, ",") {
|
|
||||||
s = strings.TrimSpace(s)
|
|
||||||
if err := newVal.Set(s); err != nil {
|
|
||||||
return fmt.Errorf("could not parse %s as int slice value for flag %s: %s", envVal, f.Name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
f.Value = newVal
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
if f.Value == nil {
|
|
||||||
f.Value = &IntSlice{}
|
|
||||||
}
|
|
||||||
set.Var(f.Value, name, f.Usage)
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int64Slice is an opaque type for []int to satisfy flag.Value and flag.Getter
|
|
||||||
type Int64Slice []int64
|
|
||||||
|
|
||||||
// Set parses the value into an integer and appends it to the list of values
|
|
||||||
func (f *Int64Slice) Set(value string) error {
|
|
||||||
tmp, err := strconv.ParseInt(value, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*f = append(*f, tmp)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value (for usage defaults)
|
|
||||||
func (f *Int64Slice) String() string {
|
|
||||||
return fmt.Sprintf("%#v", *f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value returns the slice of ints set by this flag
|
|
||||||
func (f *Int64Slice) Value() []int64 {
|
|
||||||
return *f
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns the slice of ints set by this flag
|
|
||||||
func (f *Int64Slice) Get() interface{} {
|
|
||||||
return *f
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
|
||||||
// Ignores errors
|
|
||||||
func (f Int64SliceFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||||
func (f Int64SliceFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
if f.EnvVar != "" {
|
|
||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
|
||||||
newVal := &Int64Slice{}
|
|
||||||
for _, s := range strings.Split(envVal, ",") {
|
|
||||||
s = strings.TrimSpace(s)
|
|
||||||
if err := newVal.Set(s); err != nil {
|
|
||||||
return fmt.Errorf("could not parse %s as int64 slice value for flag %s: %s", envVal, f.Name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
f.Value = newVal
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
if f.Value == nil {
|
|
||||||
f.Value = &Int64Slice{}
|
|
||||||
}
|
|
||||||
set.Var(f.Value, name, f.Usage)
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
|
||||||
// Ignores errors
|
|
||||||
func (f BoolFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||||
func (f BoolFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
val := false
|
|
||||||
if f.EnvVar != "" {
|
|
||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
|
||||||
if envVal == "" {
|
|
||||||
val = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
envValBool, err := strconv.ParseBool(envVal)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
val = envValBool
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
if f.Destination != nil {
|
|
||||||
set.BoolVar(f.Destination, name, val, f.Usage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
set.Bool(name, val, f.Usage)
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
|
||||||
// Ignores errors
|
|
||||||
func (f BoolTFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||||
func (f BoolTFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
val := true
|
|
||||||
if f.EnvVar != "" {
|
|
||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
|
||||||
if envVal == "" {
|
|
||||||
val = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
envValBool, err := strconv.ParseBool(envVal)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
val = envValBool
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
if f.Destination != nil {
|
|
||||||
set.BoolVar(f.Destination, name, val, f.Usage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
set.Bool(name, val, f.Usage)
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
|
||||||
// Ignores errors
|
|
||||||
func (f StringFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||||
func (f StringFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
if f.EnvVar != "" {
|
|
||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
|
||||||
f.Value = envVal
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
if f.Destination != nil {
|
|
||||||
set.StringVar(f.Destination, name, f.Value, f.Usage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
set.String(name, f.Value, f.Usage)
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
|
||||||
// Ignores errors
|
|
||||||
func (f IntFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||||
func (f IntFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
if f.EnvVar != "" {
|
|
||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
|
||||||
envValInt, err := strconv.ParseInt(envVal, 0, 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err)
|
|
||||||
}
|
|
||||||
f.Value = int(envValInt)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
if f.Destination != nil {
|
|
||||||
set.IntVar(f.Destination, name, f.Value, f.Usage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
set.Int(name, f.Value, f.Usage)
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
|
||||||
// Ignores errors
|
|
||||||
func (f Int64Flag) Apply(set *flag.FlagSet) {
|
|
||||||
f.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||||
func (f Int64Flag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
if f.EnvVar != "" {
|
|
||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
|
||||||
envValInt, err := strconv.ParseInt(envVal, 0, 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Value = envValInt
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
if f.Destination != nil {
|
|
||||||
set.Int64Var(f.Destination, name, f.Value, f.Usage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
set.Int64(name, f.Value, f.Usage)
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
|
||||||
// Ignores errors
|
|
||||||
func (f UintFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||||
func (f UintFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
if f.EnvVar != "" {
|
|
||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
|
||||||
envValInt, err := strconv.ParseUint(envVal, 0, 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not parse %s as uint value for flag %s: %s", envVal, f.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Value = uint(envValInt)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
if f.Destination != nil {
|
|
||||||
set.UintVar(f.Destination, name, f.Value, f.Usage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
set.Uint(name, f.Value, f.Usage)
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
|
||||||
// Ignores errors
|
|
||||||
func (f Uint64Flag) Apply(set *flag.FlagSet) {
|
|
||||||
f.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||||
func (f Uint64Flag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
if f.EnvVar != "" {
|
|
||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
|
||||||
envValInt, err := strconv.ParseUint(envVal, 0, 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not parse %s as uint64 value for flag %s: %s", envVal, f.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Value = uint64(envValInt)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
if f.Destination != nil {
|
|
||||||
set.Uint64Var(f.Destination, name, f.Value, f.Usage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
set.Uint64(name, f.Value, f.Usage)
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
|
||||||
// Ignores errors
|
|
||||||
func (f DurationFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||||
func (f DurationFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
if f.EnvVar != "" {
|
|
||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
|
||||||
envValDuration, err := time.ParseDuration(envVal)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not parse %s as duration for flag %s: %s", envVal, f.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Value = envValDuration
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
if f.Destination != nil {
|
|
||||||
set.DurationVar(f.Destination, name, f.Value, f.Usage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
set.Duration(name, f.Value, f.Usage)
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
|
||||||
// Ignores errors
|
|
||||||
func (f Float64Flag) Apply(set *flag.FlagSet) {
|
|
||||||
f.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||||
func (f Float64Flag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
if f.EnvVar != "" {
|
|
||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
|
||||||
envValFloat, err := strconv.ParseFloat(envVal, 10)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not parse %s as float64 value for flag %s: %s", envVal, f.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Value = float64(envValFloat)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
if f.Destination != nil {
|
|
||||||
set.Float64Var(f.Destination, name, f.Value, f.Usage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
set.Float64(name, f.Value, f.Usage)
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func visibleFlags(fl []Flag) []Flag {
|
|
||||||
visible := []Flag{}
|
|
||||||
for _, flag := range fl {
|
|
||||||
field := flagValue(flag).FieldByName("Hidden")
|
|
||||||
if !field.IsValid() || !field.Bool() {
|
|
||||||
visible = append(visible, flag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return visible
|
|
||||||
}
|
|
||||||
|
|
||||||
func prefixFor(name string) (prefix string) {
|
|
||||||
if len(name) == 1 {
|
|
||||||
prefix = "-"
|
|
||||||
} else {
|
|
||||||
prefix = "--"
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the placeholder, if any, and the unquoted usage string.
|
|
||||||
func unquoteUsage(usage string) (string, string) {
|
|
||||||
for i := 0; i < len(usage); i++ {
|
|
||||||
if usage[i] == '`' {
|
|
||||||
for j := i + 1; j < len(usage); j++ {
|
|
||||||
if usage[j] == '`' {
|
|
||||||
name := usage[i+1 : j]
|
|
||||||
usage = usage[:i] + name + usage[j+1:]
|
|
||||||
return name, usage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", usage
|
|
||||||
}
|
|
||||||
|
|
||||||
func prefixedNames(fullName, placeholder string) string {
|
|
||||||
var prefixed string
|
|
||||||
parts := strings.Split(fullName, ",")
|
|
||||||
for i, name := range parts {
|
|
||||||
name = strings.Trim(name, " ")
|
|
||||||
prefixed += prefixFor(name) + name
|
|
||||||
if placeholder != "" {
|
|
||||||
prefixed += " " + placeholder
|
|
||||||
}
|
|
||||||
if i < len(parts)-1 {
|
|
||||||
prefixed += ", "
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return prefixed
|
|
||||||
}
|
|
||||||
|
|
||||||
func withEnvHint(envVar, str string) string {
|
|
||||||
envText := ""
|
|
||||||
if envVar != "" {
|
|
||||||
prefix := "$"
|
|
||||||
suffix := ""
|
|
||||||
sep := ", $"
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
prefix = "%"
|
|
||||||
suffix = "%"
|
|
||||||
sep = "%, %"
|
|
||||||
}
|
|
||||||
envText = fmt.Sprintf(" [%s%s%s]", prefix, strings.Join(strings.Split(envVar, ","), sep), suffix)
|
|
||||||
}
|
|
||||||
return str + envText
|
|
||||||
}
|
|
||||||
|
|
||||||
func flagValue(f Flag) reflect.Value {
|
|
||||||
fv := reflect.ValueOf(f)
|
|
||||||
for fv.Kind() == reflect.Ptr {
|
|
||||||
fv = reflect.Indirect(fv)
|
|
||||||
}
|
|
||||||
return fv
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringifyFlag(f Flag) string {
|
|
||||||
fv := flagValue(f)
|
|
||||||
|
|
||||||
switch f.(type) {
|
|
||||||
case IntSliceFlag:
|
|
||||||
return withEnvHint(fv.FieldByName("EnvVar").String(),
|
|
||||||
stringifyIntSliceFlag(f.(IntSliceFlag)))
|
|
||||||
case Int64SliceFlag:
|
|
||||||
return withEnvHint(fv.FieldByName("EnvVar").String(),
|
|
||||||
stringifyInt64SliceFlag(f.(Int64SliceFlag)))
|
|
||||||
case StringSliceFlag:
|
|
||||||
return withEnvHint(fv.FieldByName("EnvVar").String(),
|
|
||||||
stringifyStringSliceFlag(f.(StringSliceFlag)))
|
|
||||||
}
|
|
||||||
|
|
||||||
placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String())
|
|
||||||
|
|
||||||
needsPlaceholder := false
|
|
||||||
defaultValueString := ""
|
|
||||||
|
|
||||||
if val := fv.FieldByName("Value"); val.IsValid() {
|
|
||||||
needsPlaceholder = true
|
|
||||||
defaultValueString = fmt.Sprintf(" (default: %v)", val.Interface())
|
|
||||||
|
|
||||||
if val.Kind() == reflect.String && val.String() != "" {
|
|
||||||
defaultValueString = fmt.Sprintf(" (default: %q)", val.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if defaultValueString == " (default: )" {
|
|
||||||
defaultValueString = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if needsPlaceholder && placeholder == "" {
|
|
||||||
placeholder = defaultPlaceholder
|
|
||||||
}
|
|
||||||
|
|
||||||
usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultValueString))
|
|
||||||
|
|
||||||
return withEnvHint(fv.FieldByName("EnvVar").String(),
|
|
||||||
fmt.Sprintf("%s\t%s", prefixedNames(fv.FieldByName("Name").String(), placeholder), usageWithDefault))
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringifyIntSliceFlag(f IntSliceFlag) string {
|
|
||||||
defaultVals := []string{}
|
|
||||||
if f.Value != nil && len(f.Value.Value()) > 0 {
|
|
||||||
for _, i := range f.Value.Value() {
|
|
||||||
defaultVals = append(defaultVals, fmt.Sprintf("%d", i))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return stringifySliceFlag(f.Usage, f.Name, defaultVals)
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringifyInt64SliceFlag(f Int64SliceFlag) string {
|
|
||||||
defaultVals := []string{}
|
|
||||||
if f.Value != nil && len(f.Value.Value()) > 0 {
|
|
||||||
for _, i := range f.Value.Value() {
|
|
||||||
defaultVals = append(defaultVals, fmt.Sprintf("%d", i))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return stringifySliceFlag(f.Usage, f.Name, defaultVals)
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringifyStringSliceFlag(f StringSliceFlag) string {
|
|
||||||
defaultVals := []string{}
|
|
||||||
if f.Value != nil && len(f.Value.Value()) > 0 {
|
|
||||||
for _, s := range f.Value.Value() {
|
|
||||||
if len(s) > 0 {
|
|
||||||
defaultVals = append(defaultVals, fmt.Sprintf("%q", s))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return stringifySliceFlag(f.Usage, f.Name, defaultVals)
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringifySliceFlag(usage, name string, defaultVals []string) string {
|
|
||||||
placeholder, usage := unquoteUsage(usage)
|
|
||||||
if placeholder == "" {
|
|
||||||
placeholder = defaultPlaceholder
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultVal := ""
|
|
||||||
if len(defaultVals) > 0 {
|
|
||||||
defaultVal = fmt.Sprintf(" (default: %s)", strings.Join(defaultVals, ", "))
|
|
||||||
}
|
|
||||||
|
|
||||||
usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal))
|
|
||||||
return fmt.Sprintf("%s\t%s", prefixedNames(name, placeholder), usageWithDefault)
|
|
||||||
}
|
|
||||||
-627
@@ -1,627 +0,0 @@
|
|||||||
package cli
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// WARNING: This file is generated!
|
|
||||||
|
|
||||||
// BoolFlag is a flag with type bool
|
|
||||||
type BoolFlag struct {
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
Destination *bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f BoolFlag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f BoolFlag) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bool looks up the value of a local BoolFlag, returns
|
|
||||||
// false if not found
|
|
||||||
func (c *Context) Bool(name string) bool {
|
|
||||||
return lookupBool(name, c.flagSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalBool looks up the value of a global BoolFlag, returns
|
|
||||||
// false if not found
|
|
||||||
func (c *Context) GlobalBool(name string) bool {
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
|
||||||
return lookupBool(name, fs)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupBool(name string, set *flag.FlagSet) bool {
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {
|
|
||||||
parsed, err := strconv.ParseBool(f.Value.String())
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// BoolTFlag is a flag with type bool that is true by default
|
|
||||||
type BoolTFlag struct {
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
Destination *bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f BoolTFlag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f BoolTFlag) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// BoolT looks up the value of a local BoolTFlag, returns
|
|
||||||
// false if not found
|
|
||||||
func (c *Context) BoolT(name string) bool {
|
|
||||||
return lookupBoolT(name, c.flagSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalBoolT looks up the value of a global BoolTFlag, returns
|
|
||||||
// false if not found
|
|
||||||
func (c *Context) GlobalBoolT(name string) bool {
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
|
||||||
return lookupBoolT(name, fs)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupBoolT(name string, set *flag.FlagSet) bool {
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {
|
|
||||||
parsed, err := strconv.ParseBool(f.Value.String())
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// DurationFlag is a flag with type time.Duration (see https://golang.org/pkg/time/#ParseDuration)
|
|
||||||
type DurationFlag struct {
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
Value time.Duration
|
|
||||||
Destination *time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f DurationFlag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f DurationFlag) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Duration looks up the value of a local DurationFlag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) Duration(name string) time.Duration {
|
|
||||||
return lookupDuration(name, c.flagSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalDuration looks up the value of a global DurationFlag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) GlobalDuration(name string) time.Duration {
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
|
||||||
return lookupDuration(name, fs)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupDuration(name string, set *flag.FlagSet) time.Duration {
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {
|
|
||||||
parsed, err := time.ParseDuration(f.Value.String())
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Float64Flag is a flag with type float64
|
|
||||||
type Float64Flag struct {
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
Value float64
|
|
||||||
Destination *float64
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f Float64Flag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f Float64Flag) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Float64 looks up the value of a local Float64Flag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) Float64(name string) float64 {
|
|
||||||
return lookupFloat64(name, c.flagSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalFloat64 looks up the value of a global Float64Flag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) GlobalFloat64(name string) float64 {
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
|
||||||
return lookupFloat64(name, fs)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupFloat64(name string, set *flag.FlagSet) float64 {
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {
|
|
||||||
parsed, err := strconv.ParseFloat(f.Value.String(), 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenericFlag is a flag with type Generic
|
|
||||||
type GenericFlag struct {
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
Value Generic
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f GenericFlag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f GenericFlag) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generic looks up the value of a local GenericFlag, returns
|
|
||||||
// nil if not found
|
|
||||||
func (c *Context) Generic(name string) interface{} {
|
|
||||||
return lookupGeneric(name, c.flagSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalGeneric looks up the value of a global GenericFlag, returns
|
|
||||||
// nil if not found
|
|
||||||
func (c *Context) GlobalGeneric(name string) interface{} {
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
|
||||||
return lookupGeneric(name, fs)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupGeneric(name string, set *flag.FlagSet) interface{} {
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {
|
|
||||||
parsed, err := f.Value, error(nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int64Flag is a flag with type int64
|
|
||||||
type Int64Flag struct {
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
Value int64
|
|
||||||
Destination *int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f Int64Flag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f Int64Flag) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int64 looks up the value of a local Int64Flag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) Int64(name string) int64 {
|
|
||||||
return lookupInt64(name, c.flagSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalInt64 looks up the value of a global Int64Flag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) GlobalInt64(name string) int64 {
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
|
||||||
return lookupInt64(name, fs)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupInt64(name string, set *flag.FlagSet) int64 {
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {
|
|
||||||
parsed, err := strconv.ParseInt(f.Value.String(), 0, 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntFlag is a flag with type int
|
|
||||||
type IntFlag struct {
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
Value int
|
|
||||||
Destination *int
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f IntFlag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f IntFlag) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int looks up the value of a local IntFlag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) Int(name string) int {
|
|
||||||
return lookupInt(name, c.flagSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalInt looks up the value of a global IntFlag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) GlobalInt(name string) int {
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
|
||||||
return lookupInt(name, fs)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupInt(name string, set *flag.FlagSet) int {
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {
|
|
||||||
parsed, err := strconv.ParseInt(f.Value.String(), 0, 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return int(parsed)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntSliceFlag is a flag with type *IntSlice
|
|
||||||
type IntSliceFlag struct {
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
Value *IntSlice
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f IntSliceFlag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f IntSliceFlag) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntSlice looks up the value of a local IntSliceFlag, returns
|
|
||||||
// nil if not found
|
|
||||||
func (c *Context) IntSlice(name string) []int {
|
|
||||||
return lookupIntSlice(name, c.flagSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalIntSlice looks up the value of a global IntSliceFlag, returns
|
|
||||||
// nil if not found
|
|
||||||
func (c *Context) GlobalIntSlice(name string) []int {
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
|
||||||
return lookupIntSlice(name, fs)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupIntSlice(name string, set *flag.FlagSet) []int {
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {
|
|
||||||
parsed, err := (f.Value.(*IntSlice)).Value(), error(nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int64SliceFlag is a flag with type *Int64Slice
|
|
||||||
type Int64SliceFlag struct {
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
Value *Int64Slice
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f Int64SliceFlag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f Int64SliceFlag) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int64Slice looks up the value of a local Int64SliceFlag, returns
|
|
||||||
// nil if not found
|
|
||||||
func (c *Context) Int64Slice(name string) []int64 {
|
|
||||||
return lookupInt64Slice(name, c.flagSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalInt64Slice looks up the value of a global Int64SliceFlag, returns
|
|
||||||
// nil if not found
|
|
||||||
func (c *Context) GlobalInt64Slice(name string) []int64 {
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
|
||||||
return lookupInt64Slice(name, fs)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupInt64Slice(name string, set *flag.FlagSet) []int64 {
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {
|
|
||||||
parsed, err := (f.Value.(*Int64Slice)).Value(), error(nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// StringFlag is a flag with type string
|
|
||||||
type StringFlag struct {
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
Value string
|
|
||||||
Destination *string
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f StringFlag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f StringFlag) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// String looks up the value of a local StringFlag, returns
|
|
||||||
// "" if not found
|
|
||||||
func (c *Context) String(name string) string {
|
|
||||||
return lookupString(name, c.flagSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalString looks up the value of a global StringFlag, returns
|
|
||||||
// "" if not found
|
|
||||||
func (c *Context) GlobalString(name string) string {
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
|
||||||
return lookupString(name, fs)
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupString(name string, set *flag.FlagSet) string {
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {
|
|
||||||
parsed, err := f.Value.String(), error(nil)
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// StringSliceFlag is a flag with type *StringSlice
|
|
||||||
type StringSliceFlag struct {
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
Value *StringSlice
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f StringSliceFlag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f StringSliceFlag) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// StringSlice looks up the value of a local StringSliceFlag, returns
|
|
||||||
// nil if not found
|
|
||||||
func (c *Context) StringSlice(name string) []string {
|
|
||||||
return lookupStringSlice(name, c.flagSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalStringSlice looks up the value of a global StringSliceFlag, returns
|
|
||||||
// nil if not found
|
|
||||||
func (c *Context) GlobalStringSlice(name string) []string {
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
|
||||||
return lookupStringSlice(name, fs)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupStringSlice(name string, set *flag.FlagSet) []string {
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {
|
|
||||||
parsed, err := (f.Value.(*StringSlice)).Value(), error(nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uint64Flag is a flag with type uint64
|
|
||||||
type Uint64Flag struct {
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
Value uint64
|
|
||||||
Destination *uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f Uint64Flag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f Uint64Flag) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uint64 looks up the value of a local Uint64Flag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) Uint64(name string) uint64 {
|
|
||||||
return lookupUint64(name, c.flagSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalUint64 looks up the value of a global Uint64Flag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) GlobalUint64(name string) uint64 {
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
|
||||||
return lookupUint64(name, fs)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupUint64(name string, set *flag.FlagSet) uint64 {
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {
|
|
||||||
parsed, err := strconv.ParseUint(f.Value.String(), 0, 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// UintFlag is a flag with type uint
|
|
||||||
type UintFlag struct {
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
Value uint
|
|
||||||
Destination *uint
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f UintFlag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f UintFlag) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uint looks up the value of a local UintFlag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) Uint(name string) uint {
|
|
||||||
return lookupUint(name, c.flagSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalUint looks up the value of a global UintFlag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) GlobalUint(name string) uint {
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
|
||||||
return lookupUint(name, fs)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupUint(name string, set *flag.FlagSet) uint {
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {
|
|
||||||
parsed, err := strconv.ParseUint(f.Value.String(), 0, 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return uint(parsed)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
-28
@@ -1,28 +0,0 @@
|
|||||||
package cli
|
|
||||||
|
|
||||||
// BashCompleteFunc is an action to execute when the bash-completion flag is set
|
|
||||||
type BashCompleteFunc func(*Context)
|
|
||||||
|
|
||||||
// BeforeFunc is an action to execute before any subcommands are run, but after
|
|
||||||
// the context is ready if a non-nil error is returned, no subcommands are run
|
|
||||||
type BeforeFunc func(*Context) error
|
|
||||||
|
|
||||||
// AfterFunc is an action to execute after any subcommands are run, but after the
|
|
||||||
// subcommand has finished it is run even if Action() panics
|
|
||||||
type AfterFunc func(*Context) error
|
|
||||||
|
|
||||||
// ActionFunc is the action to execute when no subcommands are specified
|
|
||||||
type ActionFunc func(*Context) error
|
|
||||||
|
|
||||||
// CommandNotFoundFunc is executed if the proper command cannot be found
|
|
||||||
type CommandNotFoundFunc func(*Context, string)
|
|
||||||
|
|
||||||
// OnUsageErrorFunc is executed if an usage error occurs. This is useful for displaying
|
|
||||||
// customized usage error messages. This function is able to replace the
|
|
||||||
// original error messages. If this function is not set, the "Incorrect usage"
|
|
||||||
// is displayed and the execution is interrupted.
|
|
||||||
type OnUsageErrorFunc func(context *Context, err error, isSubcommand bool) error
|
|
||||||
|
|
||||||
// FlagStringFunc is used by the help generation to display a flag, which is
|
|
||||||
// expected to be a single line.
|
|
||||||
type FlagStringFunc func(Flag) string
|
|
||||||
-255
@@ -1,255 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
"""
|
|
||||||
The flag types that ship with the cli library have many things in common, and
|
|
||||||
so we can take advantage of the `go generate` command to create much of the
|
|
||||||
source code from a list of definitions. These definitions attempt to cover
|
|
||||||
the parts that vary between flag types, and should evolve as needed.
|
|
||||||
|
|
||||||
An example of the minimum definition needed is:
|
|
||||||
|
|
||||||
{
|
|
||||||
"name": "SomeType",
|
|
||||||
"type": "sometype",
|
|
||||||
"context_default": "nil"
|
|
||||||
}
|
|
||||||
|
|
||||||
In this example, the code generated for the `cli` package will include a type
|
|
||||||
named `SomeTypeFlag` that is expected to wrap a value of type `sometype`.
|
|
||||||
Fetching values by name via `*cli.Context` will default to a value of `nil`.
|
|
||||||
|
|
||||||
A more complete, albeit somewhat redundant, example showing all available
|
|
||||||
definition keys is:
|
|
||||||
|
|
||||||
{
|
|
||||||
"name": "VeryMuchType",
|
|
||||||
"type": "*VeryMuchType",
|
|
||||||
"value": true,
|
|
||||||
"dest": false,
|
|
||||||
"doctail": " which really only wraps a []float64, oh well!",
|
|
||||||
"context_type": "[]float64",
|
|
||||||
"context_default": "nil",
|
|
||||||
"parser": "parseVeryMuchType(f.Value.String())",
|
|
||||||
"parser_cast": "[]float64(parsed)"
|
|
||||||
}
|
|
||||||
|
|
||||||
The meaning of each field is as follows:
|
|
||||||
|
|
||||||
name (string) - The type "name", which will be suffixed with
|
|
||||||
`Flag` when generating the type definition
|
|
||||||
for `cli` and the wrapper type for `altsrc`
|
|
||||||
type (string) - The type that the generated `Flag` type for `cli`
|
|
||||||
is expected to "contain" as its `.Value` member
|
|
||||||
value (bool) - Should the generated `cli` type have a `Value`
|
|
||||||
member?
|
|
||||||
dest (bool) - Should the generated `cli` type support a
|
|
||||||
destination pointer?
|
|
||||||
doctail (string) - Additional docs for the `cli` flag type comment
|
|
||||||
context_type (string) - The literal type used in the `*cli.Context`
|
|
||||||
reader func signature
|
|
||||||
context_default (string) - The literal value used as the default by the
|
|
||||||
`*cli.Context` reader funcs when no value is
|
|
||||||
present
|
|
||||||
parser (string) - Literal code used to parse the flag `f`,
|
|
||||||
expected to have a return signature of
|
|
||||||
(value, error)
|
|
||||||
parser_cast (string) - Literal code used to cast the `parsed` value
|
|
||||||
returned from the `parser` code
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import print_function, unicode_literals
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
import textwrap
|
|
||||||
|
|
||||||
|
|
||||||
class _FancyFormatter(argparse.ArgumentDefaultsHelpFormatter,
|
|
||||||
argparse.RawDescriptionHelpFormatter):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def main(sysargs=sys.argv[:]):
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description='Generate flag type code!',
|
|
||||||
formatter_class=_FancyFormatter)
|
|
||||||
parser.add_argument(
|
|
||||||
'package',
|
|
||||||
type=str, default='cli', choices=_WRITEFUNCS.keys(),
|
|
||||||
help='Package for which flag types will be generated'
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'-i', '--in-json',
|
|
||||||
type=argparse.FileType('r'),
|
|
||||||
default=sys.stdin,
|
|
||||||
help='Input JSON file which defines each type to be generated'
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'-o', '--out-go',
|
|
||||||
type=argparse.FileType('w'),
|
|
||||||
default=sys.stdout,
|
|
||||||
help='Output file/stream to which generated source will be written'
|
|
||||||
)
|
|
||||||
parser.epilog = __doc__
|
|
||||||
|
|
||||||
args = parser.parse_args(sysargs[1:])
|
|
||||||
_generate_flag_types(_WRITEFUNCS[args.package], args.out_go, args.in_json)
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
def _generate_flag_types(writefunc, output_go, input_json):
|
|
||||||
types = json.load(input_json)
|
|
||||||
|
|
||||||
tmp = tempfile.NamedTemporaryFile(suffix='.go', delete=False)
|
|
||||||
writefunc(tmp, types)
|
|
||||||
tmp.close()
|
|
||||||
|
|
||||||
new_content = subprocess.check_output(
|
|
||||||
['goimports', tmp.name]
|
|
||||||
).decode('utf-8')
|
|
||||||
|
|
||||||
print(new_content, file=output_go, end='')
|
|
||||||
output_go.flush()
|
|
||||||
os.remove(tmp.name)
|
|
||||||
|
|
||||||
|
|
||||||
def _set_typedef_defaults(typedef):
|
|
||||||
typedef.setdefault('doctail', '')
|
|
||||||
typedef.setdefault('context_type', typedef['type'])
|
|
||||||
typedef.setdefault('dest', True)
|
|
||||||
typedef.setdefault('value', True)
|
|
||||||
typedef.setdefault('parser', 'f.Value, error(nil)')
|
|
||||||
typedef.setdefault('parser_cast', 'parsed')
|
|
||||||
|
|
||||||
|
|
||||||
def _write_cli_flag_types(outfile, types):
|
|
||||||
_fwrite(outfile, """\
|
|
||||||
package cli
|
|
||||||
|
|
||||||
// WARNING: This file is generated!
|
|
||||||
|
|
||||||
""")
|
|
||||||
|
|
||||||
for typedef in types:
|
|
||||||
_set_typedef_defaults(typedef)
|
|
||||||
|
|
||||||
_fwrite(outfile, """\
|
|
||||||
// {name}Flag is a flag with type {type}{doctail}
|
|
||||||
type {name}Flag struct {{
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
""".format(**typedef))
|
|
||||||
|
|
||||||
if typedef['value']:
|
|
||||||
_fwrite(outfile, """\
|
|
||||||
Value {type}
|
|
||||||
""".format(**typedef))
|
|
||||||
|
|
||||||
if typedef['dest']:
|
|
||||||
_fwrite(outfile, """\
|
|
||||||
Destination *{type}
|
|
||||||
""".format(**typedef))
|
|
||||||
|
|
||||||
_fwrite(outfile, "\n}\n\n")
|
|
||||||
|
|
||||||
_fwrite(outfile, """\
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f {name}Flag) String() string {{
|
|
||||||
return FlagStringer(f)
|
|
||||||
}}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f {name}Flag) GetName() string {{
|
|
||||||
return f.Name
|
|
||||||
}}
|
|
||||||
|
|
||||||
// {name} looks up the value of a local {name}Flag, returns
|
|
||||||
// {context_default} if not found
|
|
||||||
func (c *Context) {name}(name string) {context_type} {{
|
|
||||||
return lookup{name}(name, c.flagSet)
|
|
||||||
}}
|
|
||||||
|
|
||||||
// Global{name} looks up the value of a global {name}Flag, returns
|
|
||||||
// {context_default} if not found
|
|
||||||
func (c *Context) Global{name}(name string) {context_type} {{
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {{
|
|
||||||
return lookup{name}(name, fs)
|
|
||||||
}}
|
|
||||||
return {context_default}
|
|
||||||
}}
|
|
||||||
|
|
||||||
func lookup{name}(name string, set *flag.FlagSet) {context_type} {{
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {{
|
|
||||||
parsed, err := {parser}
|
|
||||||
if err != nil {{
|
|
||||||
return {context_default}
|
|
||||||
}}
|
|
||||||
return {parser_cast}
|
|
||||||
}}
|
|
||||||
return {context_default}
|
|
||||||
}}
|
|
||||||
""".format(**typedef))
|
|
||||||
|
|
||||||
|
|
||||||
def _write_altsrc_flag_types(outfile, types):
|
|
||||||
_fwrite(outfile, """\
|
|
||||||
package altsrc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"gopkg.in/urfave/cli.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// WARNING: This file is generated!
|
|
||||||
|
|
||||||
""")
|
|
||||||
|
|
||||||
for typedef in types:
|
|
||||||
_set_typedef_defaults(typedef)
|
|
||||||
|
|
||||||
_fwrite(outfile, """\
|
|
||||||
// {name}Flag is the flag type that wraps cli.{name}Flag to allow
|
|
||||||
// for other values to be specified
|
|
||||||
type {name}Flag struct {{
|
|
||||||
cli.{name}Flag
|
|
||||||
set *flag.FlagSet
|
|
||||||
}}
|
|
||||||
|
|
||||||
// New{name}Flag creates a new {name}Flag
|
|
||||||
func New{name}Flag(fl cli.{name}Flag) *{name}Flag {{
|
|
||||||
return &{name}Flag{{{name}Flag: fl, set: nil}}
|
|
||||||
}}
|
|
||||||
|
|
||||||
// Apply saves the flagSet for later usage calls, then calls the
|
|
||||||
// wrapped {name}Flag.Apply
|
|
||||||
func (f *{name}Flag) Apply(set *flag.FlagSet) {{
|
|
||||||
f.set = set
|
|
||||||
f.{name}Flag.Apply(set)
|
|
||||||
}}
|
|
||||||
|
|
||||||
// ApplyWithError saves the flagSet for later usage calls, then calls the
|
|
||||||
// wrapped {name}Flag.ApplyWithError
|
|
||||||
func (f *{name}Flag) ApplyWithError(set *flag.FlagSet) error {{
|
|
||||||
f.set = set
|
|
||||||
return f.{name}Flag.ApplyWithError(set)
|
|
||||||
}}
|
|
||||||
""".format(**typedef))
|
|
||||||
|
|
||||||
|
|
||||||
def _fwrite(outfile, text):
|
|
||||||
print(textwrap.dedent(text), end='', file=outfile)
|
|
||||||
|
|
||||||
|
|
||||||
_WRITEFUNCS = {
|
|
||||||
'cli': _write_cli_flag_types,
|
|
||||||
'altsrc': _write_altsrc_flag_types
|
|
||||||
}
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.exit(main())
|
|
||||||
-338
@@ -1,338 +0,0 @@
|
|||||||
package cli
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"text/tabwriter"
|
|
||||||
"text/template"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AppHelpTemplate is the text template for the Default help topic.
|
|
||||||
// cli.go uses text/template to render templates. You can
|
|
||||||
// render custom help text by setting this variable.
|
|
||||||
var AppHelpTemplate = `NAME:
|
|
||||||
{{.Name}}{{if .Usage}} - {{.Usage}}{{end}}
|
|
||||||
|
|
||||||
USAGE:
|
|
||||||
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}}
|
|
||||||
|
|
||||||
VERSION:
|
|
||||||
{{.Version}}{{end}}{{end}}{{if .Description}}
|
|
||||||
|
|
||||||
DESCRIPTION:
|
|
||||||
{{.Description}}{{end}}{{if len .Authors}}
|
|
||||||
|
|
||||||
AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}:
|
|
||||||
{{range $index, $author := .Authors}}{{if $index}}
|
|
||||||
{{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}}
|
|
||||||
|
|
||||||
COMMANDS:{{range .VisibleCategories}}{{if .Name}}
|
|
||||||
{{.Name}}:{{end}}{{range .VisibleCommands}}
|
|
||||||
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
|
|
||||||
|
|
||||||
GLOBAL OPTIONS:
|
|
||||||
{{range $index, $option := .VisibleFlags}}{{if $index}}
|
|
||||||
{{end}}{{$option}}{{end}}{{end}}{{if .Copyright}}
|
|
||||||
|
|
||||||
COPYRIGHT:
|
|
||||||
{{.Copyright}}{{end}}
|
|
||||||
`
|
|
||||||
|
|
||||||
// CommandHelpTemplate is the text template for the command help topic.
|
|
||||||
// cli.go uses text/template to render templates. You can
|
|
||||||
// render custom help text by setting this variable.
|
|
||||||
var CommandHelpTemplate = `NAME:
|
|
||||||
{{.HelpName}} - {{.Usage}}
|
|
||||||
|
|
||||||
USAGE:
|
|
||||||
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}}
|
|
||||||
|
|
||||||
CATEGORY:
|
|
||||||
{{.Category}}{{end}}{{if .Description}}
|
|
||||||
|
|
||||||
DESCRIPTION:
|
|
||||||
{{.Description}}{{end}}{{if .VisibleFlags}}
|
|
||||||
|
|
||||||
OPTIONS:
|
|
||||||
{{range .VisibleFlags}}{{.}}
|
|
||||||
{{end}}{{end}}
|
|
||||||
`
|
|
||||||
|
|
||||||
// SubcommandHelpTemplate is the text template for the subcommand help topic.
|
|
||||||
// cli.go uses text/template to render templates. You can
|
|
||||||
// render custom help text by setting this variable.
|
|
||||||
var SubcommandHelpTemplate = `NAME:
|
|
||||||
{{.HelpName}} - {{if .Description}}{{.Description}}{{else}}{{.Usage}}{{end}}
|
|
||||||
|
|
||||||
USAGE:
|
|
||||||
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}
|
|
||||||
|
|
||||||
COMMANDS:{{range .VisibleCategories}}{{if .Name}}
|
|
||||||
{{.Name}}:{{end}}{{range .VisibleCommands}}
|
|
||||||
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}
|
|
||||||
{{end}}{{if .VisibleFlags}}
|
|
||||||
OPTIONS:
|
|
||||||
{{range .VisibleFlags}}{{.}}
|
|
||||||
{{end}}{{end}}
|
|
||||||
`
|
|
||||||
|
|
||||||
var helpCommand = Command{
|
|
||||||
Name: "help",
|
|
||||||
Aliases: []string{"h"},
|
|
||||||
Usage: "Shows a list of commands or help for one command",
|
|
||||||
ArgsUsage: "[command]",
|
|
||||||
Action: func(c *Context) error {
|
|
||||||
args := c.Args()
|
|
||||||
if args.Present() {
|
|
||||||
return ShowCommandHelp(c, args.First())
|
|
||||||
}
|
|
||||||
|
|
||||||
ShowAppHelp(c)
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var helpSubcommand = Command{
|
|
||||||
Name: "help",
|
|
||||||
Aliases: []string{"h"},
|
|
||||||
Usage: "Shows a list of commands or help for one command",
|
|
||||||
ArgsUsage: "[command]",
|
|
||||||
Action: func(c *Context) error {
|
|
||||||
args := c.Args()
|
|
||||||
if args.Present() {
|
|
||||||
return ShowCommandHelp(c, args.First())
|
|
||||||
}
|
|
||||||
|
|
||||||
return ShowSubcommandHelp(c)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prints help for the App or Command
|
|
||||||
type helpPrinter func(w io.Writer, templ string, data interface{})
|
|
||||||
|
|
||||||
// Prints help for the App or Command with custom template function.
|
|
||||||
type helpPrinterCustom func(w io.Writer, templ string, data interface{}, customFunc map[string]interface{})
|
|
||||||
|
|
||||||
// HelpPrinter is a function that writes the help output. If not set a default
|
|
||||||
// is used. The function signature is:
|
|
||||||
// func(w io.Writer, templ string, data interface{})
|
|
||||||
var HelpPrinter helpPrinter = printHelp
|
|
||||||
|
|
||||||
// HelpPrinterCustom is same as HelpPrinter but
|
|
||||||
// takes a custom function for template function map.
|
|
||||||
var HelpPrinterCustom helpPrinterCustom = printHelpCustom
|
|
||||||
|
|
||||||
// VersionPrinter prints the version for the App
|
|
||||||
var VersionPrinter = printVersion
|
|
||||||
|
|
||||||
// ShowAppHelpAndExit - Prints the list of subcommands for the app and exits with exit code.
|
|
||||||
func ShowAppHelpAndExit(c *Context, exitCode int) {
|
|
||||||
ShowAppHelp(c)
|
|
||||||
os.Exit(exitCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShowAppHelp is an action that displays the help.
|
|
||||||
func ShowAppHelp(c *Context) (err error) {
|
|
||||||
if c.App.CustomAppHelpTemplate == "" {
|
|
||||||
HelpPrinter(c.App.Writer, AppHelpTemplate, c.App)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
customAppData := func() map[string]interface{} {
|
|
||||||
if c.App.ExtraInfo == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return map[string]interface{}{
|
|
||||||
"ExtraInfo": c.App.ExtraInfo,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
HelpPrinterCustom(c.App.Writer, c.App.CustomAppHelpTemplate, c.App, customAppData())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultAppComplete prints the list of subcommands as the default app completion method
|
|
||||||
func DefaultAppComplete(c *Context) {
|
|
||||||
for _, command := range c.App.Commands {
|
|
||||||
if command.Hidden {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, name := range command.Names() {
|
|
||||||
fmt.Fprintln(c.App.Writer, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShowCommandHelpAndExit - exits with code after showing help
|
|
||||||
func ShowCommandHelpAndExit(c *Context, command string, code int) {
|
|
||||||
ShowCommandHelp(c, command)
|
|
||||||
os.Exit(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShowCommandHelp prints help for the given command
|
|
||||||
func ShowCommandHelp(ctx *Context, command string) error {
|
|
||||||
// show the subcommand help for a command with subcommands
|
|
||||||
if command == "" {
|
|
||||||
HelpPrinter(ctx.App.Writer, SubcommandHelpTemplate, ctx.App)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range ctx.App.Commands {
|
|
||||||
if c.HasName(command) {
|
|
||||||
if c.CustomHelpTemplate != "" {
|
|
||||||
HelpPrinterCustom(ctx.App.Writer, c.CustomHelpTemplate, c, nil)
|
|
||||||
} else {
|
|
||||||
HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.App.CommandNotFound == nil {
|
|
||||||
return NewExitError(fmt.Sprintf("No help topic for '%v'", command), 3)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.App.CommandNotFound(ctx, command)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShowSubcommandHelp prints help for the given subcommand
|
|
||||||
func ShowSubcommandHelp(c *Context) error {
|
|
||||||
return ShowCommandHelp(c, c.Command.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShowVersion prints the version number of the App
|
|
||||||
func ShowVersion(c *Context) {
|
|
||||||
VersionPrinter(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func printVersion(c *Context) {
|
|
||||||
fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShowCompletions prints the lists of commands within a given context
|
|
||||||
func ShowCompletions(c *Context) {
|
|
||||||
a := c.App
|
|
||||||
if a != nil && a.BashComplete != nil {
|
|
||||||
a.BashComplete(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShowCommandCompletions prints the custom completions for a given command
|
|
||||||
func ShowCommandCompletions(ctx *Context, command string) {
|
|
||||||
c := ctx.App.Command(command)
|
|
||||||
if c != nil && c.BashComplete != nil {
|
|
||||||
c.BashComplete(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func printHelpCustom(out io.Writer, templ string, data interface{}, customFunc map[string]interface{}) {
|
|
||||||
funcMap := template.FuncMap{
|
|
||||||
"join": strings.Join,
|
|
||||||
}
|
|
||||||
if customFunc != nil {
|
|
||||||
for key, value := range customFunc {
|
|
||||||
funcMap[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
w := tabwriter.NewWriter(out, 1, 8, 2, ' ', 0)
|
|
||||||
t := template.Must(template.New("help").Funcs(funcMap).Parse(templ))
|
|
||||||
err := t.Execute(w, data)
|
|
||||||
if err != nil {
|
|
||||||
// If the writer is closed, t.Execute will fail, and there's nothing
|
|
||||||
// we can do to recover.
|
|
||||||
if os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != "" {
|
|
||||||
fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR: %#v\n", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
func printHelp(out io.Writer, templ string, data interface{}) {
|
|
||||||
printHelpCustom(out, templ, data, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkVersion(c *Context) bool {
|
|
||||||
found := false
|
|
||||||
if VersionFlag.GetName() != "" {
|
|
||||||
eachName(VersionFlag.GetName(), func(name string) {
|
|
||||||
if c.GlobalBool(name) || c.Bool(name) {
|
|
||||||
found = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return found
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkHelp(c *Context) bool {
|
|
||||||
found := false
|
|
||||||
if HelpFlag.GetName() != "" {
|
|
||||||
eachName(HelpFlag.GetName(), func(name string) {
|
|
||||||
if c.GlobalBool(name) || c.Bool(name) {
|
|
||||||
found = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return found
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkCommandHelp(c *Context, name string) bool {
|
|
||||||
if c.Bool("h") || c.Bool("help") {
|
|
||||||
ShowCommandHelp(c, name)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkSubcommandHelp(c *Context) bool {
|
|
||||||
if c.Bool("h") || c.Bool("help") {
|
|
||||||
ShowSubcommandHelp(c)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkShellCompleteFlag(a *App, arguments []string) (bool, []string) {
|
|
||||||
if !a.EnableBashCompletion {
|
|
||||||
return false, arguments
|
|
||||||
}
|
|
||||||
|
|
||||||
pos := len(arguments) - 1
|
|
||||||
lastArg := arguments[pos]
|
|
||||||
|
|
||||||
if lastArg != "--"+BashCompletionFlag.GetName() {
|
|
||||||
return false, arguments
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, arguments[:pos]
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkCompletions(c *Context) bool {
|
|
||||||
if !c.shellComplete {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if args := c.Args(); args.Present() {
|
|
||||||
name := args.First()
|
|
||||||
if cmd := c.App.Command(name); cmd != nil {
|
|
||||||
// let the command handle the completion
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ShowCompletions(c)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkCommandCompletions(c *Context, name string) bool {
|
|
||||||
if !c.shellComplete {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
ShowCommandCompletions(c, name)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
-122
@@ -1,122 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
|
|
||||||
from subprocess import check_call, check_output
|
|
||||||
|
|
||||||
|
|
||||||
PACKAGE_NAME = os.environ.get(
|
|
||||||
'CLI_PACKAGE_NAME', 'github.com/urfave/cli'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def main(sysargs=sys.argv[:]):
|
|
||||||
targets = {
|
|
||||||
'vet': _vet,
|
|
||||||
'test': _test,
|
|
||||||
'gfmrun': _gfmrun,
|
|
||||||
'toc': _toc,
|
|
||||||
'gen': _gen,
|
|
||||||
}
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
parser.add_argument(
|
|
||||||
'target', nargs='?', choices=tuple(targets.keys()), default='test'
|
|
||||||
)
|
|
||||||
args = parser.parse_args(sysargs[1:])
|
|
||||||
|
|
||||||
targets[args.target]()
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
def _test():
|
|
||||||
if check_output('go version'.split()).split()[2] < 'go1.2':
|
|
||||||
_run('go test -v .')
|
|
||||||
return
|
|
||||||
|
|
||||||
coverprofiles = []
|
|
||||||
for subpackage in ['', 'altsrc']:
|
|
||||||
coverprofile = 'cli.coverprofile'
|
|
||||||
if subpackage != '':
|
|
||||||
coverprofile = '{}.coverprofile'.format(subpackage)
|
|
||||||
|
|
||||||
coverprofiles.append(coverprofile)
|
|
||||||
|
|
||||||
_run('go test -v'.split() + [
|
|
||||||
'-coverprofile={}'.format(coverprofile),
|
|
||||||
('{}/{}'.format(PACKAGE_NAME, subpackage)).rstrip('/')
|
|
||||||
])
|
|
||||||
|
|
||||||
combined_name = _combine_coverprofiles(coverprofiles)
|
|
||||||
_run('go tool cover -func={}'.format(combined_name))
|
|
||||||
os.remove(combined_name)
|
|
||||||
|
|
||||||
|
|
||||||
def _gfmrun():
|
|
||||||
go_version = check_output('go version'.split()).split()[2]
|
|
||||||
if go_version < 'go1.3':
|
|
||||||
print('runtests: skip on {}'.format(go_version), file=sys.stderr)
|
|
||||||
return
|
|
||||||
_run(['gfmrun', '-c', str(_gfmrun_count()), '-s', 'README.md'])
|
|
||||||
|
|
||||||
|
|
||||||
def _vet():
|
|
||||||
_run('go vet ./...')
|
|
||||||
|
|
||||||
|
|
||||||
def _toc():
|
|
||||||
_run('node_modules/.bin/markdown-toc -i README.md')
|
|
||||||
_run('git diff --exit-code')
|
|
||||||
|
|
||||||
|
|
||||||
def _gen():
|
|
||||||
go_version = check_output('go version'.split()).split()[2]
|
|
||||||
if go_version < 'go1.5':
|
|
||||||
print('runtests: skip on {}'.format(go_version), file=sys.stderr)
|
|
||||||
return
|
|
||||||
|
|
||||||
_run('go generate ./...')
|
|
||||||
_run('git diff --exit-code')
|
|
||||||
|
|
||||||
|
|
||||||
def _run(command):
|
|
||||||
if hasattr(command, 'split'):
|
|
||||||
command = command.split()
|
|
||||||
print('runtests: {}'.format(' '.join(command)), file=sys.stderr)
|
|
||||||
check_call(command)
|
|
||||||
|
|
||||||
|
|
||||||
def _gfmrun_count():
|
|
||||||
with open('README.md') as infile:
|
|
||||||
lines = infile.read().splitlines()
|
|
||||||
return len(filter(_is_go_runnable, lines))
|
|
||||||
|
|
||||||
|
|
||||||
def _is_go_runnable(line):
|
|
||||||
return line.startswith('package main')
|
|
||||||
|
|
||||||
|
|
||||||
def _combine_coverprofiles(coverprofiles):
|
|
||||||
combined = tempfile.NamedTemporaryFile(
|
|
||||||
suffix='.coverprofile', delete=False
|
|
||||||
)
|
|
||||||
combined.write('mode: set\n')
|
|
||||||
|
|
||||||
for coverprofile in coverprofiles:
|
|
||||||
with open(coverprofile, 'r') as infile:
|
|
||||||
for line in infile.readlines():
|
|
||||||
if not line.startswith('mode: '):
|
|
||||||
combined.write(line)
|
|
||||||
|
|
||||||
combined.flush()
|
|
||||||
name = combined.name
|
|
||||||
combined.close()
|
|
||||||
return name
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.exit(main())
|
|
||||||
-241
@@ -1,241 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"regexp"
|
|
||||||
)
|
|
||||||
|
|
||||||
// link message struct
|
|
||||||
type LinkMsg struct {
|
|
||||||
Title string `json:"title"`
|
|
||||||
MessageURL string `json:"messageURL"`
|
|
||||||
PicURL string `json:"picURL"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// action card message struct
|
|
||||||
type ActionCard struct {
|
|
||||||
Text string `json:"text"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
SingleTitle string `json:"singleTitle"`
|
|
||||||
SingleURL string `json:"singleURL"`
|
|
||||||
BtnOrientation string `json:"btnOrientation"`
|
|
||||||
HideAvatar string `json:"hideAvatar"` // robot message avatar
|
|
||||||
Buttons []struct {
|
|
||||||
Title string `json:"title"`
|
|
||||||
ActionURL string `json:"actionURL"`
|
|
||||||
} `json:"btns"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// payload
|
|
||||||
type PayLoad struct {
|
|
||||||
MsgType string `json:"msgtype"`
|
|
||||||
Text struct {
|
|
||||||
Content string `json:"content"`
|
|
||||||
} `json:"text"`
|
|
||||||
Link struct {
|
|
||||||
Title string `json:"title"`
|
|
||||||
Text string `json:"text"`
|
|
||||||
PicUrl string `json:"picUrl"`
|
|
||||||
MessageUrl string `json:"messageUrl"`
|
|
||||||
} `json:"link"`
|
|
||||||
Markdown struct {
|
|
||||||
Title string `json:"title"`
|
|
||||||
Text string `json:"text"`
|
|
||||||
} `json:"markdown"`
|
|
||||||
ActionCard ActionCard `json:"actionCard"`
|
|
||||||
FeedCard struct {
|
|
||||||
Links []LinkMsg `json:"links"`
|
|
||||||
} `json:"feedCard"`
|
|
||||||
At struct {
|
|
||||||
AtMobiles []string `json:"atMobiles"`
|
|
||||||
IsAtAll bool `json:"isAtAll"`
|
|
||||||
} `json:"at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// web hook base config
|
|
||||||
type WebHook struct {
|
|
||||||
AccessToken string `json:"accessToken"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewWebHook(accessToken string) *WebHook {
|
|
||||||
return &WebHook{AccessToken: accessToken}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Response struct {
|
|
||||||
ErrorCode int `json:"errcode"`
|
|
||||||
ErrorMessage string `json:"errmsg"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var baseApi = "https://oapi.dingtalk.com/robot/send?access_token="
|
|
||||||
var reg = `^1([38][0-9]|14[57]|5[^4])\d{8}$`
|
|
||||||
var regx = regexp.MustCompile(reg)
|
|
||||||
|
|
||||||
// real send request to api
|
|
||||||
func (w *WebHook) sendPayload(payload *PayLoad) error {
|
|
||||||
// get config
|
|
||||||
bs, err := json.Marshal(payload)
|
|
||||||
if nil != err {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// request api
|
|
||||||
resp, err := http.Post(baseApi+w.AccessToken, "application/json", bytes.NewReader(bs))
|
|
||||||
if nil != err {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// read response body
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if nil != err {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// api unusual
|
|
||||||
if 200 != resp.StatusCode {
|
|
||||||
return fmt.Errorf("%d: %s", resp.StatusCode, string(body))
|
|
||||||
}
|
|
||||||
|
|
||||||
var result Response
|
|
||||||
// json decode
|
|
||||||
err = json.Unmarshal(body, &result)
|
|
||||||
if nil != err {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if 0 != result.ErrorCode {
|
|
||||||
return fmt.Errorf("%d: %s", result.ErrorCode, result.ErrorMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// text message
|
|
||||||
func (w *WebHook) SendTextMsg(content string, isAtAll bool, mobiles ...string) error {
|
|
||||||
// send request
|
|
||||||
return w.sendPayload(&PayLoad{
|
|
||||||
MsgType: "text",
|
|
||||||
Text: struct {
|
|
||||||
Content string `json:"content"`
|
|
||||||
}{
|
|
||||||
Content: content,
|
|
||||||
},
|
|
||||||
At: struct {
|
|
||||||
AtMobiles []string `json:"atMobiles"`
|
|
||||||
IsAtAll bool `json:"isAtAll"`
|
|
||||||
}{
|
|
||||||
AtMobiles: mobiles,
|
|
||||||
IsAtAll: isAtAll,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// with link message
|
|
||||||
func (w *WebHook) SendLinkMsg(title, content, picURL, msgURL string) error {
|
|
||||||
return w.sendPayload(&PayLoad{
|
|
||||||
MsgType: "link",
|
|
||||||
Link: struct {
|
|
||||||
Title string `json:"title"`
|
|
||||||
Text string `json:"text"`
|
|
||||||
PicUrl string `json:"picUrl"`
|
|
||||||
MessageUrl string `json:"messageUrl"`
|
|
||||||
}{
|
|
||||||
Title: title,
|
|
||||||
Text: content,
|
|
||||||
PicUrl: picURL,
|
|
||||||
MessageUrl: msgURL,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// send markdown msg
|
|
||||||
func (w *WebHook) SendMarkdownMsg(title, content string, isAtAll bool, mobiles ...string) error {
|
|
||||||
firstLine := false
|
|
||||||
for _, mobile := range mobiles {
|
|
||||||
if regx.MatchString(mobile) {
|
|
||||||
if false == firstLine {
|
|
||||||
content += "#####"
|
|
||||||
}
|
|
||||||
content += " @" + mobile
|
|
||||||
firstLine = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// send request
|
|
||||||
return w.sendPayload(&PayLoad{
|
|
||||||
MsgType: "markdown",
|
|
||||||
Markdown: struct {
|
|
||||||
Title string `json:"title"`
|
|
||||||
Text string `json:"text"`
|
|
||||||
}{
|
|
||||||
Title: title,
|
|
||||||
Text: content,
|
|
||||||
},
|
|
||||||
At: struct {
|
|
||||||
AtMobiles []string `json:"atMobiles"`
|
|
||||||
IsAtAll bool `json:"isAtAll"`
|
|
||||||
}{
|
|
||||||
AtMobiles: mobiles,
|
|
||||||
IsAtAll: isAtAll,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// send single action card
|
|
||||||
func (w *WebHook) SendActionCardMsg(title, content string, linkTitles, linkUrls []string, hideAvatar, btnOrientation bool) error {
|
|
||||||
// validation is empty
|
|
||||||
if 0 == len(linkTitles) || 0 == len(linkUrls) {
|
|
||||||
return errors.New("links or titles is empty!")
|
|
||||||
}
|
|
||||||
// validation is equal
|
|
||||||
if len(linkUrls) != len(linkTitles) {
|
|
||||||
return errors.New("links length and titles length is not equal!")
|
|
||||||
}
|
|
||||||
// hide robot avatar
|
|
||||||
var strHideAvatar = "0"
|
|
||||||
if hideAvatar {
|
|
||||||
strHideAvatar = "1"
|
|
||||||
}
|
|
||||||
// button sort
|
|
||||||
var strBtnOrientation = "0"
|
|
||||||
if btnOrientation {
|
|
||||||
strBtnOrientation = "1"
|
|
||||||
}
|
|
||||||
// button struct
|
|
||||||
var buttons []struct {
|
|
||||||
Title string `json:"title"`
|
|
||||||
ActionURL string `json:"actionURL"`
|
|
||||||
}
|
|
||||||
// inject to button
|
|
||||||
for i := 0; i < len(linkTitles); i++ {
|
|
||||||
buttons = append(buttons, struct {
|
|
||||||
Title string `json:"title"`
|
|
||||||
ActionURL string `json:"actionURL"`
|
|
||||||
}{
|
|
||||||
Title: linkTitles[i],
|
|
||||||
ActionURL: linkUrls[i],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// send request
|
|
||||||
return w.sendPayload(&PayLoad{
|
|
||||||
MsgType: "actionCard",
|
|
||||||
ActionCard: ActionCard{
|
|
||||||
Title: title,
|
|
||||||
Text: content,
|
|
||||||
HideAvatar: strHideAvatar,
|
|
||||||
BtnOrientation: strBtnOrientation,
|
|
||||||
Buttons: buttons,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// send link card message
|
|
||||||
func (w *WebHook) SendLinkCardMsg(messages []LinkMsg) error {
|
|
||||||
return w.sendPayload(&PayLoad{
|
|
||||||
MsgType: "feedCard",
|
|
||||||
FeedCard: struct {
|
|
||||||
Links []LinkMsg `json:"links"`
|
|
||||||
}{
|
|
||||||
Links: messages,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user