Compare commits

...

53 Commits

Author SHA1 Message Date
dependabot[bot] 4db9815ece build(deps): bump github.com/urfave/cli from 1.22.14 to 1.22.15
Bumps [github.com/urfave/cli](https://github.com/urfave/cli) from 1.22.14 to 1.22.15.
- [Release notes](https://github.com/urfave/cli/releases)
- [Changelog](https://github.com/urfave/cli/blob/main/docs/CHANGELOG.md)
- [Commits](https://github.com/urfave/cli/compare/v1.22.14...v1.22.15)

---
updated-dependencies:
- dependency-name: github.com/urfave/cli
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-27 07:11:55 +08:00
lddsb eed2f0fae1 fix: Fix release failure caused by deprecated archives->replacements 2024-04-14 07:48:52 +08:00
lddsb ca944a4a26 fix: Fix issues that do not exist in Golang version 1.15.6
Replace specific version numbers with stable versions
2024-04-13 19:28:05 +08:00
lddsb 07310794cb perf: Optimize color setting logic
Allow color code strings to start with #
2024-04-13 18:32:09 +08:00
lddsb a165a28c60 fix: README typo 2024-04-13 18:21:15 +08:00
dependabot[bot] 43222b966a build(deps): bump github.com/urfave/cli from 1.22.5 to 1.22.14
Bumps [github.com/urfave/cli](https://github.com/urfave/cli) from 1.22.5 to 1.22.14.
- [Release notes](https://github.com/urfave/cli/releases)
- [Changelog](https://github.com/urfave/cli/blob/main/docs/CHANGELOG.md)
- [Commits](https://github.com/urfave/cli/compare/v1.22.5...v1.22.14)

---
updated-dependencies:
- dependency-name: github.com/urfave/cli
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-13 18:09:19 +08:00
dependabot[bot] 530b471503 build(deps): bump github.com/joho/godotenv from 1.4.0 to 1.5.1
Bumps [github.com/joho/godotenv](https://github.com/joho/godotenv) from 1.4.0 to 1.5.1.
- [Release notes](https://github.com/joho/godotenv/releases)
- [Commits](https://github.com/joho/godotenv/compare/v1.4.0...v1.5.1)

---
updated-dependencies:
- dependency-name: github.com/joho/godotenv
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-11 20:31:25 +08:00
internelp 3a0957f479 Use exec instead of Docker for execution 2024-04-11 20:31:13 +08:00
dependabot[bot] c9a57df515 build(deps): bump github.com/joho/godotenv from 1.3.0 to 1.4.0
Bumps [github.com/joho/godotenv](https://github.com/joho/godotenv) from 1.3.0 to 1.4.0.
- [Release notes](https://github.com/joho/godotenv/releases)
- [Commits](https://github.com/joho/godotenv/compare/v1.3.0...v1.4.0)

---
updated-dependencies:
- dependency-name: github.com/joho/godotenv
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-28 09:41:01 +08:00
lddsb d0a4fc64e4 feat: support string as a tpl 2021-08-26 09:29:12 +08:00
lddsb 526b8eec46 docs: update README for at mobiles 2021-07-02 23:13:05 +08:00
lddsb 8729ae05b4 feat: add at mobiles tpl 2021-07-02 23:01:24 +08:00
dependabot[bot] cec9f81f52 build(deps): bump github.com/lddsb/dingtalk-webhook from 0.0.4 to 0.0.5
Bumps [github.com/lddsb/dingtalk-webhook](https://github.com/lddsb/dingtalk-webhook) from 0.0.4 to 0.0.5.
- [Release notes](https://github.com/lddsb/dingtalk-webhook/releases)
- [Commits](https://github.com/lddsb/dingtalk-webhook/compare/v0.0.4...v0.0.5)

---
updated-dependencies:
- dependency-name: github.com/lddsb/dingtalk-webhook
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-02 22:01:57 +08:00
dependabot-preview[bot] 428577470d Upgrade to GitHub-native Dependabot 2021-06-26 23:30:42 +08:00
lddsb 7c8a85bcc4 fix: release.yaml go-version error 2020-12-19 15:53:16 +08:00
lddsb ecf4f89e0a feat: use goreleaser for release tool 2020-12-19 15:39:10 +08:00
lddsb e7ef2de488 feat: support custom started at and finished at 2020-12-19 11:59:45 +08:00
lddsb acd708ada3 docs: update readme and changelog for 1.2.5 2020-12-16 17:48:07 +08:00
lddsb 5bcf803346 fix: docker image tags missing 2020-12-16 14:58:38 +08:00
lddsb 1b219624cd feat: GitHub Actions for build and push docker image 2020-12-16 14:14:51 +08:00
lddsb d409acce48 feat: use os.Getenv() read environment variables for tpl 2020-12-16 11:58:09 +08:00
dependabot-preview[bot] 2e94325c0e Bump github.com/urfave/cli from 1.22.4 to 1.22.5 (#27)
Bumps [github.com/urfave/cli](https://github.com/urfave/cli) from 1.22.4 to 1.22.5.
- [Release notes](https://github.com/urfave/cli/releases)
- [Changelog](https://github.com/urfave/cli/blob/master/docs/CHANGELOG.md)
- [Commits](https://github.com/urfave/cli/compare/v1.22.4...v1.22.5)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-11-28 13:36:40 +08:00
lddsb 1622a2beaa perf: update readme and changelog 2020-04-29 18:40:45 +08:00
dependabot-preview[bot] d2ab02e29c Bump github.com/urfave/cli from 1.22.3 to 1.22.4 (#23)
Bumps [github.com/urfave/cli](https://github.com/urfave/cli) from 1.22.3 to 1.22.4.
- [Release notes](https://github.com/urfave/cli/releases)
- [Changelog](https://github.com/urfave/cli/blob/master/docs/CHANGELOG.md)
- [Commits](https://github.com/urfave/cli/compare/v1.22.3...v1.22.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-29 11:09:42 +08:00
lddsb d0284b2434 Merge tag 'v1.2.4'
patch for k8s missing env
2020-04-29 11:08:21 +08:00
lddsb c8bc5d889d fix: patch for k8s missing env 2020-04-29 10:59:46 +08:00
Dee Luo d4dc439671 Create CHANGELOG.md 2020-03-10 22:43:40 +08:00
dependabot-preview[bot] 8d0434b308 Bump github.com/urfave/cli from 1.22.2 to 1.22.3 (#20)
Bumps [github.com/urfave/cli](https://github.com/urfave/cli) from 1.22.2 to 1.22.3.
- [Release notes](https://github.com/urfave/cli/releases)
- [Changelog](https://github.com/urfave/cli/blob/master/docs/CHANGELOG.md)
- [Commits](https://github.com/urfave/cli/compare/v1.22.2...v1.22.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-10 18:13:02 +08:00
Dee Luo 8aafd04b82 fix: default image cdn url (#17) 2020-01-20 11:51:20 +08:00
lddsb a651a73cbd fix: default image cdn url 2020-01-20 11:36:34 +08:00
Dee Luo 7c68447d1d feat: support sign (#13) 2019-12-02 18:07:13 +08:00
Dee Luo 183999881b Merge pull request #12 from lddsb/dependabot/go_modules/github.com/lddsb/dingtalk-webhook-0.0.2
Bump github.com/lddsb/dingtalk-webhook from 0.0.1 to 0.0.2
2019-11-30 15:07:55 +08:00
dependabot-preview[bot] 4c52115e2a Bump github.com/lddsb/dingtalk-webhook from 0.0.1 to 0.0.2
Bumps [github.com/lddsb/dingtalk-webhook](https://github.com/lddsb/dingtalk-webhook) from 0.0.1 to 0.0.2.
- [Release notes](https://github.com/lddsb/dingtalk-webhook/releases)
- [Commits](https://github.com/lddsb/dingtalk-webhook/compare/v0.0.1...0.0.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-30 02:41:16 +00:00
lddsb fb601a0d6c feat: add dependabot badge 2019-11-26 08:55:49 +08:00
Dee Luo 8bd745becf Merge pull request #10 from lddsb/dependabot/go_modules/github.com/urfave/cli-1.22.2
Bump github.com/urfave/cli from 1.20.0 to 1.22.2
2019-11-26 08:43:38 +08:00
dependabot-preview[bot] e9aea90aac Bump github.com/urfave/cli from 1.20.0 to 1.22.2
Bumps [github.com/urfave/cli](https://github.com/urfave/cli) from 1.20.0 to 1.22.2.
- [Release notes](https://github.com/urfave/cli/releases)
- [Changelog](https://github.com/urfave/cli/blob/master/docs/CHANGELOG.md)
- [Commits](https://github.com/urfave/cli/compare/v1.20.0...v1.22.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-26 00:40:21 +00:00
Dee Luo c77b865a43 fix: read me adjustment 2019-11-25 23:19:23 +08:00
Dee Luo fbfbec3bde Merge pull request #9 from lddsb/feature/readme-zh
feat: add readme_zh
2019-11-25 23:03:10 +08:00
lddsb 30c2324f4e feat: add readme_zh 2019-11-25 22:49:34 +08:00
Dee Luo 11d34af11a Merge pull request #6 from lddsb/bugfix/tips-title-support
add tips title option, and add exit code for exception
2019-11-07 22:04:09 +08:00
lddsb f9aff987bb add tips title option, and add exit code for exception 2019-11-07 21:43:57 +08:00
Dee Luo 8164de1292 Merge pull request #4 from lddsb/feature/msg-tpl
complete tpl feature
2019-09-20 17:13:42 +08:00
lddsb 9cb8a9b160 fix dry run keyword 2019-09-20 17:04:05 +08:00
lddsb 9eed74739b fix docker build dry run 2019-09-20 16:20:04 +08:00
lddsb 664c2e90c2 complete tpl feature 2019-09-20 10:20:35 +08:00
lddsb 8d080c3b0c remove codebeat badge and drone ci badge, add new badge for docker cloud auto build status 2019-09-09 11:54:07 +08:00
lddsb 0734cc50ff transfer automatic build to DockerHub 2019-09-09 11:35:20 +08:00
lddsb 00fb440336 update readme.md about development 2019-09-08 10:28:53 +08:00
lddsb 580a43e7a1 add editorconfig file 2019-09-07 22:19:32 +08:00
lddsb 5ed5fe4bdc make drone.yml more elegant 2019-03-09 10:52:59 +08:00
lddsb 31767312c3 replace dep with go mod 2019-03-09 10:50:29 +08:00
lddsb 88dcf2d231 remove main_test.go 2019-03-09 00:23:13 +08:00
lddsb 38be4d2982 optimized drone ci trigger 2019-03-08 23:51:01 +08:00
50 changed files with 1236 additions and 6353 deletions
+55 -26
View File
@@ -1,46 +1,75 @@
---
kind: pipeline
name: default
workspace:
base: /go
path: src/github.com/lddsb/drone-dingtalk-message
name: testing
steps:
- name: build
- name: vet
pull: always
image: golang
commands:
- go get -u github.com/golang/dep/cmd/dep
- dep ensure
- CGO_ENABLED=0 GOOS=linux go build -a -o drone-dingtalk-message .
- go test -race -coverprofile=coverage.txt -covermode=atomic
- ./drone-dingtalk-message -h
- 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
when:
status:
- success
settings:
token:
from_secret: codecov_token
- name: publish
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
when:
status:
- success
settings:
repo: lddsb/drone-dingtalk-message
cache_from: lddsb/drone-dingtalk-message
dockerfile: Dockerfile
dry_run: true
repo: lddsb/drone-dingtalk-message
tags:
- latest
- 1.0.0
username:
from_secret: docker_username
password:
from_secret: docker_password
trigger:
branch:
- master
event:
- tags
- pull_request
depends_on:
- testing
+2
View File
@@ -0,0 +1,2 @@
[*.y*ml]
indent_size = 2
+8
View File
@@ -0,0 +1,8 @@
version: 2
updates:
- package-ecosystem: gomod
directory: "/"
schedule:
interval: daily
time: "10:00"
open-pull-requests-limit: 10
+55
View File
@@ -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 }}
+25
View File
@@ -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
View File
@@ -1,4 +1,7 @@
dive.log
drone-dingtalk-message
.idea
vendor
vendor
coverage.txt
coverage.out
env.list
+49
View File
@@ -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 }}_
{{- title .Os }}_
{{- if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "386" }}i386
{{- else }}{{ .Arch }}{{ end }}
{{- if .Arm }}v{{ .Arm }}{{ end -}}
format_overrides:
- goos: windows
format: zip
files:
- README.md
- LICENSE
- tpls/*
+72
View File
@@ -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
View File
@@ -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
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 && \
apk add \
ca-certificates && \
rm -rf /var/cache/apk/*
ADD drone-dingtalk-message /bin/
ENTRYPOINT ["/bin/drone-dingtalk-message"]
ENTRYPOINT ["/bin/drone-dingtalk"]
Generated
-40
View File
@@ -1,40 +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]]
branch = "master"
digest = "1:9142979c770f3d0f3c42c2eec532048bbbe2571134da91e8946cd8610c85c04b"
name = "github.com/lddsb/dingtalk-webhook"
packages = ["."]
pruneopts = "UT"
revision = "b4abe34b5fa9af8ea7d5f28c02bd314558b21f7f"
[[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/lddsb/dingtalk-webhook",
"github.com/urfave/cli",
]
solver-name = "gps-cdcl"
solver-version = 1
-42
View File
@@ -1,42 +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
[[constraint]]
branch = "master"
name = "github.com/lddsb/dingtalk-webhook"
+11
View File
@@ -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
+143 -36
View File
@@ -1,30 +1,61 @@
# Drone CI DingTalk Message Plugin
[![Build Status](https://drone.lddsb.com/api/badges/lddsb/drone-dingtalk-message/status.svg)](https://drone.lddsb.com/lddsb/drone-dingtalk-message) [![Go Report Card](https://goreportcard.com/badge/github.com/lddsb/drone-dingtalk-message)](https://goreportcard.com/report/github.com/lddsb/drone-dingtalk-message) [![codecov](https://codecov.io/gh/lddsb/drone-dingtalk-message/branch/master/graph/badge.svg)](https://codecov.io/gh/lddsb/drone-dingtalk-message) [![codebeat badge](https://codebeat.co/badges/23f68b84-1fd2-4f29-8467-9285c1e0facc)](https://codebeat.co/projects/github-com-lddsb-drone-dingtalk-message-master) [![LICENSE: MIT](https://img.shields.io/github/license/lddsb/drone-dingtalk-message.svg?style=flat-square)](LICENSE)
[![GitHub Actions](https://github.com/lddsb/drone-dingtalk-message/workflows/Publish%20to%20DockerHub%20and%20Github%20Package/badge.svg)](https://github.com/lddsb/drone-dingtalk-message/actions?query=workflow%3A%22Publish+to+DockerHub+and+Github+Package%22) [![Go Report Card](https://goreportcard.com/badge/github.com/lddsb/drone-dingtalk-message)](https://goreportcard.com/report/github.com/lddsb/drone-dingtalk-message) [![codecov](https://codecov.io/gh/lddsb/drone-dingtalk-message/branch/master/graph/badge.svg)](https://codecov.io/gh/lddsb/drone-dingtalk-message) [![Dependabot](https://api.dependabot.com/badges/status?host=github&repo=lddsb/drone-dingtalk-message&identifier=159822771)](https://app.dependabot.com/accounts/lddsb/repos/159822771) [![LICENSE: MIT](https://img.shields.io/github/license/lddsb/drone-dingtalk-message.svg?style=flat-square)](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
`0.8.x`
```yaml
pipeline:
...
#...
notification:
image: lddsb/drone-dingtalk-message
token: your-group-bot-token
type: markdown
```
`1.0.x`
`1.x`
```yaml
kind: pipeline
name: default
steps:
...
#...
- name: notification
image: lddsb/drone-dingtalk-message
settings:
token: your-groupbot-token
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
@@ -36,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.
`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`.
`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`.
`sha_link`(when `type=markdown`)
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`)
`success_pic`
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.
`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
- Send Success
@@ -89,28 +189,35 @@ String. You can customize the picture for the `build failure` message by this op
![markdown-massage-customize](https://i.imgur.com/xFrCTZp.jpg)
### Todo
- Multi-Type
- Multi-Lang
- More User Customization
### Development
We use `go mod` to manage dependencies, so it's easy to build.
- First get this repo
- get this repo
```shell
go get github.com/lddsb/drone-dingtalk-message
```
- get dependent lib
```shell
dep ensure
$ git clone https://github.com/lddsb/drone-dingtalk-message.git /path/to/you/want
```
- build
```shell
cd $GOPATH/src/github.com/lddsb/drone-dingtalk-message && go build .
$ cd /path/to/you/want && GO111MODULE=on go build .
```
- run
```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
View File
@@ -0,0 +1,226 @@
# Drone CI的钉钉群组机器人通知插件
[![GitHub Actions](https://github.com/lddsb/drone-dingtalk-message/workflows/Publish%20to%20DockerHub%20and%20Github%20Package/badge.svg)](https://github.com/lddsb/drone-dingtalk-message/actions?query=workflow%3A%22Publish+to+DockerHub+and+Github+Package%22) [![Go Report Card](https://goreportcard.com/badge/github.com/lddsb/drone-dingtalk-message)](https://goreportcard.com/report/github.com/lddsb/drone-dingtalk-message) [![codecov](https://codecov.io/gh/lddsb/drone-dingtalk-message/branch/master/graph/badge.svg)](https://codecov.io/gh/lddsb/drone-dingtalk-message) [![Dependabot](https://api.dependabot.com/badges/status?host=github&repo=lddsb/drone-dingtalk-message&identifier=159822771)](https://app.dependabot.com/accounts/lddsb/repos/159822771) [![LICENSE: MIT](https://img.shields.io/github/license/lddsb/drone-dingtalk-message.svg?style=flat-square)](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
![send-success](https://i.imgur.com/cECppkW.jpg)
- 忘记填写Access TokenDrone Web
![missing-access-token](https://i.imgur.com/Su7iiyw.jpg)
- 忘记填写消息类型或者不支持的消息类型
![message-type-error](https://i.imgur.com/qtJ4DsA.jpg)
- 默认的`markdown`消息
![markdown-message-default](https://i.imgur.com/Bl7cT1y.jpg)
- 带颜色和链接的`markdown`消息
![markdown-massage-customize](https://i.imgur.com/pzdFzIw.jpg)
- 带颜色、链接和图片的`markdown`消息
![markdown-massage-customize](https://i.imgur.com/xFrCTZp.jpg)
### 贡献代码
本项目使用了`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`
+9
View File
@@ -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.15
)
+31
View File
@@ -0,0 +1,31 @@
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/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/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
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/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/urfave/cli v1.22.15 h1:nuqt+pdC/KqswQKhETJjo7pvn/k4xMUxgW6liI7XpnM=
github.com/urfave/cli v1.22.15/go.mod h1:wSan1hmo5zeyLGBjRJbzRTNk8gwoYa2B9n4q9dmRIc0=
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=
+181 -63
View File
@@ -4,19 +4,21 @@ import (
"fmt"
"log"
"os"
"time"
_ "github.com/joho/godotenv/autoload"
"github.com/joho/godotenv"
"github.com/urfave/cli"
)
// Version of cli
var Version = "0.1.1202"
var Version = "0.2.1219"
func main() {
app := cli.NewApp()
app.Name = "Drone Dingtalk Message Plugin"
app.Usage = "Sending message to Dingtalk group by robot using webhook"
app.Copyright = "© 2018 Dee Luo"
app.Name = "Drone DingTalk Message Plugin"
app.Usage = "Sending message to DingTalk group by robot using WebHook"
year := time.Now().Year()
app.Copyright = fmt.Sprintf("© 2018-%d Dee Luo", year)
app.Authors = []cli.Author{
{
Name: "Dee Luo",
@@ -27,36 +29,45 @@ func main() {
app.Version = Version
app.Flags = []cli.Flag{
cli.BoolFlag{
Name: "config.debug",
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{
Name: "config.token,access_token,token",
Usage: "dingtalk webhook access token",
Usage: "DingTalk webhook access token",
EnvVar: "PLUGIN_ACCESS_TOKEN,PLUGIN_TOKEN",
},
cli.StringFlag{
Name: "config.lang",
Value: "zh_CN",
Usage: "the lang display (zh_CN or en_US, zh_CN is default)",
EnvVar: "PLUGIN_LANG",
Name: "config.secret,secret",
Usage: "DingTalk WebHook secret for generate sign",
EnvVar: "PLUGIN_SECRET",
},
cli.StringFlag{
Name: "config.message.type,message_type",
Usage: "dingtalk message type, like text, markdown, action card, link and feed card...",
Name: "config.message.type,message_type,type",
Usage: "DingTalk message type, like text, markdown, action card, link and feed card...",
EnvVar: "PLUGIN_MSG_TYPE,PLUGIN_TYPE,PLUGIN_MESSAGE_TYPE",
},
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)",
EnvVar: "PLUGIN_MSG_AT_ALL",
},
cli.StringFlag{
Name: "config.message.at.mobiles",
Usage: "at someone in a dingtalk group need this guy bind's mobile",
Name: "config.message.at.mobiles,mobiles",
Usage: "at someone in a DingTalk group need this guy bind's mobile",
EnvVar: "PLUGIN_MSG_AT_MOBILES",
},
cli.StringFlag{
Name: "commit.author.username",
Usage: "providers the author username for the current commit",
EnvVar: "DRONE_COMMIT_AUTHOR",
},
cli.StringFlag{
Name: "commit.author.avatar",
Usage: "providers the author avatar url for the current commit",
@@ -94,10 +105,45 @@ func main() {
EnvVar: "DRONE_COMMIT_SHA",
},
cli.StringFlag{
Name: "repo.fullname",
Name: "commit.ref",
Usage: "provider the commit ref for the current build",
EnvVar: "DRONE_COMMIT_REF",
},
cli.StringFlag{
Name: "repo.full.name",
Usage: "providers the full name of the repository",
EnvVar: "DRONE_REPO",
},
cli.StringFlag{
Name: "repo.name",
Usage: "provider the name of the repository",
EnvVar: "DRONE_REPO_NAME",
},
cli.StringFlag{
Name: "repo.group",
Usage: "provider the group of the repository",
EnvVar: "DRONE_REPO_NAMESPACE",
},
cli.StringFlag{
Name: "repo.remote.url",
Usage: "provider the remote url of the repository",
EnvVar: "DRONE_REMOTE_URL",
},
cli.StringFlag{
Name: "repo.owner",
Usage: "provider the owner of the repository",
EnvVar: "DRONE_REPO_OWNER",
},
cli.Uint64Flag{
Name: "stage.started",
Usage: "stage started ",
EnvVar: "DRONE_STAGE_STARTED",
},
cli.Uint64Flag{
Name: "stage.finished",
Usage: "stage finished",
EnvVar: "DRONE_STAGE_FINISHED",
},
cli.StringFlag{
Name: "build.status",
Usage: "build status",
@@ -110,40 +156,85 @@ func main() {
EnvVar: "DRONE_BUILD_LINK",
},
cli.StringFlag{
Name: "config.success.pic.url",
Usage: "config success picture url",
Name: "build.event",
Usage: "build event",
EnvVar: "DRONE_BUILD_EVENT",
},
cli.Uint64Flag{
Name: "build.started",
Usage: "build started",
EnvVar: "DRONE_BUILD_STARTED",
},
cli.Uint64Flag{
Name: "build.finished",
Usage: "build finished",
EnvVar: "DRONE_BUILD_FINISHED",
},
cli.StringFlag{
Name: "tpl.build.status.success",
Usage: "tpl.build status for replace success",
EnvVar: "TPL_BUILD_STATUS_SUCCESS, PLUGIN_TPL_BUILD_STATUS_SUCCESS",
},
cli.StringFlag{
Name: "tpl.build.status.failure",
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",
},
cli.StringFlag{
Name: "config.failure.pic.url",
Usage: "config failure picture url",
Name: "custom.pic.url.failure",
Usage: "custom failure picture url",
EnvVar: "FAILURE_PICTURE_URL,PLUGIN_FAILURE_PIC",
},
cli.StringFlag{
Name: "config.success.color",
Usage: "config success color for title in markdown",
Name: "custom.color.success",
Usage: "custom success color for title in markdown",
EnvVar: "SUCCESS_COLOR,PLUGIN_SUCCESS_COLOR",
},
cli.StringFlag{
Name: "config.failure.color",
Usage: "config failure color for title in markdown",
Name: "custom.color.failure",
Usage: "custom failure color for title in markdown",
EnvVar: "FAILURE_COLOR,PLUGIN_FAILURE_COLOR",
},
cli.BoolFlag{
Name: "config.message.color",
Usage: "configure the message with color or not",
EnvVar: "PLUGIN_COLOR,PLUGIN_MESSAGE_COLOR",
cli.StringFlag{
Name: "custom.tpl,tpl",
Usage: "custom tpl",
EnvVar: "PLUGIN_TPL,PLUGIN_CUSTOM_TPL",
},
cli.BoolFlag{
Name: "config.message.pic",
Usage: "configure the message with picture or not",
EnvVar: "PLUGIN_PIC,PLUGIN_MESSAGE_PIC",
cli.StringFlag{
Name: "tpl.repo.full.name",
Usage: "tpl custom repo full name",
EnvVar: "PLUGIN_TPL_REPO_FULL_NAME,TPL_REPO_FULL_NAME",
},
cli.BoolFlag{
Name: "config.message.sha.link",
Usage: "link sha source page or not",
EnvVar: "PLUGIN_SHA_LINK,PLUGIN_MESSAGE_SHA_LINK",
cli.StringFlag{
Name: "tpl.repo.short.name",
Usage: "tpl custom repo short name",
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 {
@@ -157,54 +248,81 @@ func run(c *cli.Context) {
Drone: Drone{
// repo info
Repo: Repo{
FullName: c.String("repo.fullname"),
ShortName: c.String("repo.name"),
GroupName: c.String("repo.group"),
OwnerName: c.String("repo.owner"),
RemoteURL: c.String("repo.remote.url"),
FullName: c.String("repo.full.name"),
},
// build info
Build: Build{
Status: c.String("build.status"),
Link: c.String("build.link"),
Status: c.String("build.status"),
Link: c.String("build.link"),
Event: c.String("build.event"),
StartAt: c.Uint64("build.started"),
FinishedAt: c.Uint64("build.finished"),
},
Commit: Commit{
Sha: c.String("commit.sha"),
Branch: c.String("commit.branch"),
Message: c.String("commit.message"),
Link: c.String("commit.link"),
Authors: struct {
Avatar string
Email string
Name string
}{
Avatar: c.String("commit.author.avatar"),
Email: c.String("commit.author.email"),
Name: c.String("commit.author.name"),
Author: CommitAuthor{
Avatar: c.String("commit.author.avatar"),
Email: c.String("commit.author.email"),
Name: c.String("commit.author.name"),
Username: c.String("commit.author.username"),
},
},
Stage: Stage{
StartedAt: c.Uint64("stage.started"),
FinishedAt: c.Uint64("stage.finished"),
},
},
// custom config
Config: Config{
AccessToken: c.String("config.token"),
//Lang: c.String("config.lang"),
IsAtALL: c.Bool("config.message.at.all"),
MsgType: c.String("config.message.type"),
Mobiles: c.String("config.message.at.mobiles"),
Debug: c.Bool("config.debug"),
Secret: c.String("config.secret"),
IsAtALL: c.Bool("config.message.at.all"),
MsgType: c.String("config.message.type"),
Mobiles: c.String("config.message.at.mobiles"),
Debug: c.Bool("config.debug"),
TipsTitle: c.String("config.tips.title"),
},
Extra: Extra{
Pic: ExtraPic{
WithPic: c.Bool("config.message.pic"),
SuccessPicURL: c.String("config.success.pic.url"),
FailurePicURL: c.String("config.failure.pic.url"),
Custom: Custom{
Pic: Pic{
SuccessPicURL: c.String("custom.pic.url.success"),
FailurePicURL: c.String("custom.pic.url.failure"),
},
Color: ExtraColor{
SuccessColor: c.String("config.success.color"),
FailureColor: c.String("config.failure.color"),
WithColor: c.Bool("config.message.color"),
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"),
},
},
LinkSha: c.Bool("config.message.sha.link"),
},
}
if err := plugin.Exec(); nil != err {
fmt.Println(err)
os.Exit(1)
}
}
-10
View File
@@ -1,10 +0,0 @@
package main
import (
"testing"
)
func TestMain(t *testing.T) {
main()
t.Log("main testing finished")
}
+299 -135
View File
@@ -3,63 +3,87 @@ package main
import (
"errors"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"regexp"
"strconv"
"strings"
webhook "github.com/lddsb/dingtalk-webhook"
)
type (
// Repo `repo base info`
// Repo repo base info
Repo struct {
FullName string // repository full name
ShortName string // short name
GroupName string // group name
FullName string // repository full name
OwnerName string // repo owner
RemoteURL string // repo remote url
}
// Build `build info`
// Build info
Build struct {
Status string // providers the current build status
Link string // providers the current build link
Status string // providers the current build status
Link string // providers the current build link
Event string // trigger event
StartAt uint64 // build start at ( unix timestamp )
FinishedAt uint64 // build finish at ( unix timestamp )
}
// Commit `commit info`
// Commit info
Commit struct {
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)
Message string // providers the commit message for the current build
Sha string // providers the commit sha for the current build
Authors CommitAuthors
Ref string // commit ref
Author CommitAuthor
}
// CommitAuthors `commit author info`
CommitAuthors struct {
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
// Stage drone stage env
Stage struct {
StartedAt uint64
FinishedAt uint64
}
// Drone `drone info`
// CommitAuthor commit author info
CommitAuthor struct {
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
Username string // the author username for the current commit
}
// Drone drone info
Drone struct {
Repo Repo
Build Build
Commit Commit
Stage Stage
}
// Config `plugin private config`
// Config plugin private config
Config struct {
Debug bool
AccessToken string
Secret string
IsAtALL bool
Mobiles string
Username string
MsgType string
TipsTitle string
}
// MessageConfig `DingTalk message struct`
// MessageConfig DingTalk message struct
MessageConfig struct {
ActionCard ActionCard
}
// ActionCard `action card message struct`
// ActionCard action card message struct
ActionCard struct {
LinkUrls string
LinkTitles string
@@ -67,56 +91,109 @@ type (
BtnOrientation bool
}
// Extra `extra variables`
Extra struct {
Color ExtraColor
Pic ExtraPic
LinkSha bool
}
// ExtraPic `extra config for pic`
ExtraPic struct {
WithPic bool
// Pic extra config for pic
Pic struct {
SuccessPicURL string
FailurePicURL string
}
// ExtraColor `extra config for color`
ExtraColor struct {
WithColor bool
// Color extra config for color
Color struct {
SuccessColor string
FailureColor string
}
// Plugin `plugin all config`
// Plugin plugin all config
Plugin struct {
Drone Drone
Config Config
Extra Extra
Tpl Tpl
Drone Drone
Config Config
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`
// Exec execute WebHook
func (p *Plugin) Exec() error {
if p.Config.Debug {
for _, e := range os.Environ() {
log.Println(e)
}
}
var err error
if 0 == len(p.Config.AccessToken) {
msg := "missing dingtalk access token"
if "" == p.Config.AccessToken {
msg := "missing DingTalk access token"
return errors.New(msg)
}
if 6 > len(p.Drone.Commit.Sha) {
return errors.New("commit sha cannot short than 6")
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
}
newWebhook := webhook.NewWebHook(p.Config.AccessToken)
mobiles := strings.Split(p.Config.Mobiles, ",")
switch strings.ToLower(p.Config.MsgType) {
case "markdown":
err = newWebhook.SendMarkdownMsg("You have a new message...", p.baseTpl(), p.Config.IsAtALL, mobiles...)
err = newWebHook.SendMarkdownMsg(p.Config.TipsTitle, tpl, p.Config.IsAtALL, mobiles...)
case "text":
err = newWebhook.SendTextMsg(p.baseTpl(), p.Config.IsAtALL, mobiles...)
err = newWebHook.SendTextMsg(tpl, p.Config.IsAtALL, mobiles...)
case "link":
err = newWebhook.SendLinkMsg(p.Drone.Build.Status, p.baseTpl(), p.Drone.Commit.Authors.Avatar, p.Drone.Build.Link)
err = newWebHook.SendLinkMsg(p.Drone.Build.Status, tpl, p.Drone.Commit.Author.Avatar, p.Drone.Build.Link)
default:
msg := "not support message type"
err = errors.New(msg)
@@ -129,90 +206,174 @@ func (p *Plugin) Exec() error {
return err
}
// markdownTpl `output the tpl of markdown`
func (p *Plugin) markdownTpl() string {
var tpl string
// title
title := fmt.Sprintf(" %s *Branch Build %s*",
strings.Title(p.Drone.Commit.Branch),
strings.Title(p.Drone.Build.Status))
// with color on title
if p.Extra.Color.WithColor {
title = fmt.Sprintf("<font color=%s>%s</font>", p.getColor(), title)
// fileExists check file is exists
func fileExists(filePath string) bool {
_, err := os.Stat(filePath)
if err != nil {
if os.IsExist(err) {
return true
}
return false
}
tpl = fmt.Sprintf("# %s \n", title)
// with pic
if p.Extra.Pic.WithPic {
tpl += fmt.Sprintf("![%s](%s)\n\n",
p.Drone.Build.Status,
p.getPicURL())
}
// commit message
commitMsg := fmt.Sprintf("%s", p.Drone.Commit.Message)
if p.Extra.Color.WithColor {
commitMsg = fmt.Sprintf("<font color=%s>%s</font>", p.getColor(), commitMsg)
}
tpl += commitMsg + "\n\n"
// sha info
commitSha := p.Drone.Commit.Sha
if p.Extra.LinkSha {
commitSha = fmt.Sprintf("[Click To %s Commit Detail Page](%s)", commitSha[:6], p.Drone.Commit.Link)
}
tpl += commitSha + "\n\n"
// author info
authorInfo := fmt.Sprintf("`%s(%s)`", p.Drone.Commit.Authors.Name, p.Drone.Commit.Authors.Email)
tpl += authorInfo + "\n\n"
// build detail link
buildDetail := fmt.Sprintf("[Click To The Build Detail Page %s](%s)",
p.getEmoticon(),
p.Drone.Build.Link)
tpl += buildDetail
return tpl
return true
}
func (p *Plugin) baseTpl() string {
tpl := ""
switch strings.ToLower(p.Config.MsgType) {
case "markdown":
tpl = p.markdownTpl()
case "text":
tpl = fmt.Sprintf(`[%s] %s
%s (%s)
@%s
%s (%s)
`,
p.Drone.Build.Status,
strings.TrimSpace(p.Drone.Commit.Message),
p.Drone.Repo.FullName,
p.Drone.Commit.Branch,
p.Drone.Commit.Sha,
p.Drone.Commit.Authors.Name,
p.Drone.Commit.Authors.Email)
case "link":
tpl = fmt.Sprintf(`%s(%s) @%s %s(%s)`,
p.Drone.Repo.FullName,
p.Drone.Commit.Branch,
p.Drone.Commit.Sha[:6],
p.Drone.Commit.Authors.Name,
p.Drone.Commit.Authors.Email)
case "actionCard":
// coming soon
// getTpl get tpl from local file or remote file
func (p *Plugin) getTpl() (tpl string, err error) {
//var tpl string
tplDir := "/app/drone/dingtalk/message/tpls"
if "" == p.Custom.Tpl {
p.Custom.Tpl = fmt.Sprintf("%s/%s.tpl", tplDir, strings.ToLower(p.Config.MsgType))
}
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
}
/**
get emoticon
*/
// getEnvs get available envs
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 {
emoticons := make(map[string]string)
emoticons["success"] = ":)"
@@ -226,44 +387,47 @@ func (p *Plugin) getEmoticon() string {
return ":("
}
/**
get picture url
*/
// get picture url
func (p *Plugin) getPicURL() string {
pics := make(map[string]string)
// success picture url
pics["success"] = "https://ws4.sinaimg.cn/large/006tNc79gy1fz05g5a7utj30he0bfjry.jpg"
if p.Extra.Pic.SuccessPicURL != "" {
pics["success"] = p.Extra.Pic.SuccessPicURL
pics["success"] = "https://wx1.sinaimg.cn/large/006tNc79gy1fz05g5a7utj30he0bfjry.jpg"
if p.Custom.Pic.SuccessPicURL != "" {
pics["success"] = p.Custom.Pic.SuccessPicURL
}
// failure picture url
pics["failure"] = "https://ws1.sinaimg.cn/large/006tNc79gy1fz0b4fghpnj30hd0bdmxn.jpg"
if p.Extra.Pic.FailurePicURL != "" {
pics["failure"] = p.Extra.Pic.FailurePicURL
pics["failure"] = "https://wx1.sinaimg.cn/large/006tNc79gy1fz0b4fghpnj30hd0bdmxn.jpg"
if p.Custom.Pic.FailurePicURL != "" {
pics["failure"] = p.Custom.Pic.FailurePicURL
}
url, ok := pics[p.Drone.Build.Status]
picURL, ok := pics[p.Drone.Build.Status]
if ok {
return url
return picURL
}
return ""
}
/**
get color for message title
*/
// get color for message title
func (p *Plugin) getColor() string {
colors := make(map[string]string)
// success color
colors["success"] = "#008000"
if p.Extra.Color.SuccessColor != "" {
colors["success"] = "#" + p.Extra.Color.SuccessColor
if p.Custom.Color.SuccessColor != "" {
if p.Custom.Color.SuccessColor[0] != '#' {
p.Custom.Color.SuccessColor = "#" + p.Custom.Color.SuccessColor
}
colors["success"] = p.Custom.Color.SuccessColor
}
// failure color
colors["failure"] = "#FF0000"
if p.Extra.Color.FailureColor != "" {
colors["failure"] = "#" + p.Extra.Color.FailureColor
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]
+23 -21
View File
@@ -12,43 +12,45 @@ func TestPlugin(t *testing.T) {
}
p.Config.AccessToken = "example-access-token"
err = p.Exec()
if nil == err {
t.Error("commit sha length error should be catch!")
}
p.Drone.Commit.Sha = "53729847dfksj"
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 = "text"
err = p.Exec()
if nil == err {
t.Error("access token invalid error should be catch!")
}
p.Config.MsgType = "link"
err = p.Exec()
if nil == err {
t.Error("access token invalid error should be catch!")
}
p.Extra.Color.WithColor = true
p.Extra.Color.FailureColor = "#555555"
p.Extra.Color.SuccessColor = "#222222"
p.Extra.Pic.WithPic = true
p.Extra.Pic.FailurePicURL = "https://www.baidu.com"
p.Extra.Pic.SuccessPicURL = "https://www.baidu.com"
p.Extra.LinkSha = true
// p.Drone.Build.Status = "failure"
p.Config.MsgType = "markdown"
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 {
+9
View File
@@ -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])
+9
View File
@@ -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])
+11
View File
@@ -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])
+5
View File
@@ -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
View File
@@ -1 +0,0 @@
.DS_Store
-8
View File
@@ -1,8 +0,0 @@
language: go
go:
- 1.x
os:
- linux
- osx
-23
View File
@@ -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
View File
@@ -1,163 +0,0 @@
# GoDotEnv [![Build Status](https://travis-ci.org/joho/godotenv.svg?branch=master)](https://travis-ci.org/joho/godotenv) [![Build status](https://ci.appveyor.com/api/projects/status/9v40vnfvvgde64u4?svg=true)](https://ci.appveyor.com/project/joho/godotenv) [![Go Report Card](https://goreportcard.com/badge/github.com/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 environmentssuch as resource handles for databases or credentials for external servicesshould 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: [![Build Status](https://travis-ci.org/joho/godotenv.svg?branch=master)](https://travis-ci.org/joho/godotenv) Windows: [![Build status](https://ci.appveyor.com/api/projects/status/9v40vnfvvgde64u4)](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
View File
@@ -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
View File
@@ -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
View File
@@ -1,2 +0,0 @@
[flake8]
max-line-length = 120
-2
View File
@@ -1,2 +0,0 @@
*.coverprofile
node_modules/
-27
View File
@@ -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
View File
@@ -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 &amp; 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 &amp; 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
-21
View File
@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2016 Jeremy Saenz & Contributors
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.
-1381
View File
File diff suppressed because it is too large Load Diff
-497
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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())