mirror of
https://github.com/git-chglog/git-chglog.git
synced 2026-01-23 18:35:06 +00:00
Compare commits
215 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83fc0386ad | ||
|
|
65fd9c7cb6 | ||
|
|
6a6993d52d | ||
|
|
09d3a61821 | ||
|
|
d93ef22384 | ||
|
|
ffadcdb2f8 | ||
|
|
09ccd2f09b | ||
|
|
d7f33d0797 | ||
|
|
78063fb71f | ||
|
|
06336286cf | ||
|
|
6ad9b5c2db | ||
|
|
26c9a7c721 | ||
|
|
f3cf5b67a0 | ||
|
|
b0f4afeff4 | ||
|
|
f608376b8d | ||
|
|
6ff4d21a0e | ||
|
|
13cb5b3dc4 | ||
|
|
1dbe8da4a3 | ||
|
|
7d4f3e8dff | ||
|
|
36320619c5 | ||
|
|
4f3ff62db6 | ||
|
|
5896ad91ce | ||
|
|
cbab5b89dc | ||
|
|
66a1d9ea5a | ||
|
|
d1dc1da744 | ||
|
|
03f0a44924 | ||
|
|
9558380243 | ||
|
|
3d8006051f | ||
|
|
2861505ecb | ||
|
|
899c1ede45 | ||
|
|
93707ab667 | ||
|
|
7a3896821b | ||
|
|
0022ecf864 | ||
|
|
82ead28bee | ||
|
|
21b98bd56f | ||
|
|
45e01f0f7f | ||
|
|
6a18b6ef90 | ||
|
|
b38b5032cf | ||
|
|
9842899ea1 | ||
|
|
ac01b30c39 | ||
|
|
cefdc53e29 | ||
|
|
c257740de9 | ||
|
|
bfac4702a7 | ||
|
|
57aad771c8 | ||
|
|
bc0f363327 | ||
|
|
fdd421b057 | ||
|
|
7cc56b1256 | ||
|
|
f76afed086 | ||
|
|
2307bff72f | ||
|
|
8d9e00b699 | ||
|
|
b02996e027 | ||
|
|
2f5b47b0f7 | ||
|
|
4f3fdc4dae | ||
|
|
e523fd471a | ||
|
|
34b9d5c997 | ||
|
|
b44eb4e011 | ||
|
|
4d8b2b6be7 | ||
|
|
3113e42524 | ||
|
|
9a0d584745 | ||
|
|
2caa67cc76 | ||
|
|
9926e07971 | ||
|
|
ebff3d0beb | ||
|
|
9a1a9a525c | ||
|
|
ae3382b7c8 | ||
|
|
2c3d3f400e | ||
|
|
c3ee560429 | ||
|
|
500a5db7e9 | ||
|
|
a7ea397268 | ||
|
|
54bb7363dc | ||
|
|
e6e7a69a7d | ||
|
|
aa2732d6a2 | ||
|
|
b70aef4adb | ||
|
|
0cc1984e29 | ||
|
|
703cfdefff | ||
|
|
9d62af2943 | ||
|
|
a1c84d7a0d | ||
|
|
8713d96856 | ||
|
|
efa1c30c0a | ||
|
|
43006610a1 | ||
|
|
8195e12833 | ||
|
|
289163a1e2 | ||
|
|
bd958e70ed | ||
|
|
73994ac8f4 | ||
|
|
6e1fc86e33 | ||
|
|
cb93c2e6e0 | ||
|
|
17b1252dd5 | ||
|
|
5d98a27559 | ||
|
|
0ba87caa43 | ||
|
|
771b567c44 | ||
|
|
cbf3fda1f5 | ||
|
|
49ea2f24a9 | ||
|
|
c41bf3ab9a | ||
|
|
d6315435a3 | ||
|
|
44f71cbcd8 | ||
|
|
75d59a9eb8 | ||
|
|
0ae5e13a06 | ||
|
|
41ad31326b | ||
|
|
db796966b3 | ||
|
|
0a4450abc1 | ||
|
|
72fb3eac14 | ||
|
|
a94e3f9a80 | ||
|
|
c1c1fe8140 | ||
|
|
2cabbd362a | ||
|
|
ec5cdfeea2 | ||
|
|
495fa2de57 | ||
|
|
15ce9db03e | ||
|
|
6050f20bcd | ||
|
|
40b31a0f68 | ||
|
|
5a55d0f51b | ||
|
|
ac693f45bc | ||
|
|
1198e283de | ||
|
|
f5df8faf8b | ||
|
|
63a4e63702 | ||
|
|
af1f71410a | ||
|
|
86d67ce2f0 | ||
|
|
b7edc16a49 | ||
|
|
dc128028e6 | ||
|
|
44fcaed553 | ||
|
|
1d2781fb22 | ||
|
|
b9e0bcd492 | ||
|
|
f53bfccee0 | ||
|
|
e407b9a96e | ||
|
|
594ec82eaf | ||
|
|
5ad4cab298 | ||
|
|
55dbf00339 | ||
|
|
87df4b477c | ||
|
|
e2e3797335 | ||
|
|
4046d94b7c | ||
|
|
83ccab2905 | ||
|
|
f8f4ccb8b7 | ||
|
|
d008bef7fb | ||
|
|
36cf6bce12 | ||
|
|
7a71844c6f | ||
|
|
cb878aff2a | ||
|
|
f97c6a022e | ||
|
|
c36bc89869 | ||
|
|
bcfe4d23cf | ||
|
|
481f6c0770 | ||
|
|
774bdce700 | ||
|
|
3f49f9338f | ||
|
|
5564081583 | ||
|
|
93f743ece9 | ||
|
|
d3e1f56e91 | ||
|
|
25c41823a3 | ||
|
|
5ce1760d0f | ||
|
|
f65b08d1f6 | ||
|
|
b1c368a0a8 | ||
|
|
d44e8ccdcb | ||
|
|
ce7ccb0db0 | ||
|
|
68c5005522 | ||
|
|
ab4e0f6039 | ||
|
|
a00a2f206f | ||
|
|
21ae9e8388 | ||
|
|
765f0c96ed | ||
|
|
65eee6228b | ||
|
|
82d0df16a8 | ||
|
|
588c3d0a41 | ||
|
|
09a216a8a1 | ||
|
|
8bc61c101d | ||
|
|
e30498689c | ||
|
|
4a9b3ef65a | ||
|
|
e9d40da0f8 | ||
|
|
b217d782eb | ||
|
|
4a68d692ab | ||
|
|
58cf56e664 | ||
|
|
081aa8df60 | ||
|
|
0a623f3f61 | ||
|
|
45ed6e3ee2 | ||
|
|
d446571b8c | ||
|
|
f11b9e6143 | ||
|
|
7a004f88eb | ||
|
|
d3f14a305d | ||
|
|
98543fb897 | ||
|
|
ed6fb2722e | ||
|
|
40598132fc | ||
|
|
790a2e6574 | ||
|
|
dd838e8675 | ||
|
|
f9ab379519 | ||
|
|
6e1bbeec22 | ||
|
|
5c456f8bca | ||
|
|
1554e383d7 | ||
|
|
88ea38ea8f | ||
|
|
51d980ff82 | ||
|
|
9067d95bd7 | ||
|
|
c284c96eae | ||
|
|
f8160a2b76 | ||
|
|
bb9daaf163 | ||
|
|
8fb0c7f482 | ||
|
|
29102c1d91 | ||
|
|
114b7d6fc8 | ||
|
|
d165ea884a | ||
|
|
8780cd1244 | ||
|
|
1b2210dbca | ||
|
|
b7385619d1 | ||
|
|
7a6d2a015d | ||
|
|
f8bac52c49 | ||
|
|
7fe88a3f61 | ||
|
|
2b81993531 | ||
|
|
d0bac0c1df | ||
|
|
45c389c6b5 | ||
|
|
b885c20585 | ||
|
|
c7ca5ce738 | ||
|
|
b01be88230 | ||
|
|
96562bcc9a | ||
|
|
65669492a0 | ||
|
|
ccd72509c6 | ||
|
|
ec0e3c56af | ||
|
|
2040904731 | ||
|
|
491028989e | ||
|
|
25bb6e17a1 | ||
|
|
a7d86469b9 | ||
|
|
aa5cf0913d | ||
|
|
34815cafe8 | ||
|
|
fc2b625deb | ||
|
|
8292ab7d2c |
401 changed files with 6193 additions and 169199 deletions
56
.chglog/CHANGELOG.tpl.md
Executable file
56
.chglog/CHANGELOG.tpl.md
Executable file
|
|
@ -0,0 +1,56 @@
|
|||
{{ if .Versions -}}
|
||||
<a name="unreleased"></a>
|
||||
## [Unreleased]
|
||||
|
||||
{{ if .Unreleased.CommitGroups -}}
|
||||
{{ range .Unreleased.CommitGroups -}}
|
||||
### {{ .Title }}
|
||||
{{ range .Commits -}}
|
||||
- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
|
||||
{{ range .Versions }}
|
||||
<a name="{{ .Tag.Name }}"></a>
|
||||
## {{ if .Tag.Previous }}[{{ .Tag.Name }}]{{ else }}{{ .Tag.Name }}{{ end }} - {{ datetime "2006-01-02" .Tag.Date }}
|
||||
{{ range .CommitGroups -}}
|
||||
### {{ .Title }}
|
||||
{{ range .Commits -}}
|
||||
- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .RevertCommits -}}
|
||||
### Reverts
|
||||
{{ range .RevertCommits -}}
|
||||
- {{ .Revert.Header }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .MergeCommits -}}
|
||||
### Pull Requests
|
||||
{{ range .MergeCommits -}}
|
||||
- {{ .Header }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .NoteGroups -}}
|
||||
{{ range .NoteGroups -}}
|
||||
### {{ .Title }}
|
||||
{{ range .Notes }}
|
||||
{{ .Body }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .Versions }}
|
||||
[Unreleased]: {{ .Info.RepositoryURL }}/compare/{{ $latest := index .Versions 0 }}{{ $latest.Tag.Name }}...HEAD
|
||||
{{ range .Versions -}}
|
||||
{{ if .Tag.Previous -}}
|
||||
[{{ .Tag.Name }}]: {{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
30
.chglog/config.yml
Executable file
30
.chglog/config.yml
Executable file
|
|
@ -0,0 +1,30 @@
|
|||
style: github
|
||||
template: CHANGELOG.tpl.md
|
||||
info:
|
||||
title: CHANGELOG
|
||||
repository_url: https://github.com/git-chglog/git-chglog
|
||||
options:
|
||||
commits:
|
||||
filters:
|
||||
Type:
|
||||
- feat
|
||||
- fix
|
||||
- perf
|
||||
- refactor
|
||||
- chore
|
||||
commit_groups:
|
||||
title_maps:
|
||||
feat: Features
|
||||
fix: Bug Fixes
|
||||
perf: Performance Improvements
|
||||
refactor: Code Refactoring
|
||||
chore: Chores
|
||||
header:
|
||||
pattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$"
|
||||
pattern_maps:
|
||||
- Type
|
||||
- Scope
|
||||
- Subject
|
||||
notes:
|
||||
keywords:
|
||||
- BREAKING CHANGE
|
||||
28
.github/ISSUE_TEMPLATE.md
vendored
Normal file
28
.github/ISSUE_TEMPLATE.md
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
<!-- Please replace {Please write here} with your description -->
|
||||
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
{Please write here}
|
||||
|
||||
|
||||
## Actual Behavior
|
||||
|
||||
{Please write here}
|
||||
|
||||
|
||||
## Steps to Reproduce (including precondition)
|
||||
|
||||
{Please write here}
|
||||
|
||||
|
||||
## Screenshot on This Problem (if possible)
|
||||
|
||||
{Please write here}
|
||||
|
||||
|
||||
## Your Environment
|
||||
|
||||
- OS: {Please write here}
|
||||
- git-chglog version: {Please write here}
|
||||
|
||||
35
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
35
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<!-- Thank you for your contribution to git-chglog! Please replace {Please write here} with your description -->
|
||||
|
||||
|
||||
## What does this do / why do we need it?
|
||||
|
||||
{Please write here}
|
||||
|
||||
|
||||
## How this PR fixes the problem?
|
||||
|
||||
{Please write here}
|
||||
|
||||
|
||||
## What should your reviewer look out for in this PR?
|
||||
|
||||
{Please write here}
|
||||
|
||||
|
||||
## Check lists
|
||||
|
||||
* [ ] Test passed
|
||||
* [ ] Coding style (indentation, etc)
|
||||
|
||||
|
||||
## Additional Comments (if any)
|
||||
|
||||
{Please write here}
|
||||
|
||||
|
||||
## Which issue(s) does this PR fix?
|
||||
|
||||
<!--
|
||||
fixes #
|
||||
fixes #
|
||||
-->
|
||||
25
.github/renovate.json
vendored
Normal file
25
.github/renovate.json
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"semanticCommits": true,
|
||||
"prHourlyLimit": 5,
|
||||
"reviewersFromCodeOwners": true,
|
||||
"labels": [
|
||||
"dependencies"
|
||||
],
|
||||
"extends": [
|
||||
"config:base",
|
||||
"group:allNonMajor"
|
||||
],
|
||||
"postUpdateOptions": ["gomodTidy"],
|
||||
"major": {
|
||||
"labels": [
|
||||
"dependencies",
|
||||
"major"
|
||||
]
|
||||
},
|
||||
"minor": {
|
||||
"labels": [
|
||||
"dependencies",
|
||||
"minor"
|
||||
]
|
||||
}
|
||||
}
|
||||
105
.github/workflows/ci.yml
vendored
Normal file
105
.github/workflows/ci.yml
vendored
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
name: ci
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: ["opened", "synchronize"]
|
||||
paths-ignore:
|
||||
- "README.md"
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- "README.md"
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.24"
|
||||
GOLANGCI_VERSION: "v1.64.8"
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: Run tests
|
||||
run: make test
|
||||
|
||||
- name: Install goveralls
|
||||
run: |
|
||||
go install github.com/mattn/goveralls@latest
|
||||
echo $GOPATH
|
||||
ls -lash $GOPATH
|
||||
which goveralls
|
||||
working-directory: /tmp
|
||||
|
||||
- name: Send coverage
|
||||
run: goveralls -coverprofile=cover.out -service=github
|
||||
env:
|
||||
COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
lint:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
version: ${{ env.GOLANGCI_VERSION }}
|
||||
|
||||
docker-image-tests:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: Run tests
|
||||
run: make build
|
||||
|
||||
- name: Test docker image
|
||||
run: |
|
||||
docker build -t git-chglog:ci-build .
|
||||
docker run -v ${GITHUB_WORKSPACE}:/workdir -w /workdir git-chglog:ci-build > ${GITHUB_WORKSPACE}/ci-build.md
|
||||
head ${GITHUB_WORKSPACE}/ci-build.md
|
||||
|
||||
goreleaser-test:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v4
|
||||
with:
|
||||
version: latest
|
||||
args: release --clean --skip=publish --snapshot
|
||||
34
.github/workflows/publish.yml
vendored
Normal file
34
.github/workflows/publish.yml
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
name: publish
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.24"
|
||||
DOCKER_REGISTRY: "quay.io"
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: Login to Docker Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.DOCKER_REGISTRY }}
|
||||
username: ${{ secrets.QUAY_IO_USER }}
|
||||
password: ${{ secrets.QUAY_IO_TOKEN }}
|
||||
|
||||
- name: Build and Push docker image
|
||||
run: DOCKER_TAG=edge make docker push
|
||||
39
.github/workflows/release.yml
vendored
Normal file
39
.github/workflows/release.yml
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
name: release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.24"
|
||||
DOCKER_REGISTRY: "quay.io"
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: Login to Docker Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.DOCKER_REGISTRY }}
|
||||
username: ${{ secrets.QUAY_IO_USER }}
|
||||
password: ${{ secrets.QUAY_IO_TOKEN }}
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v4
|
||||
with:
|
||||
version: latest
|
||||
args: release --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GORELEASER_TOKEN }}
|
||||
14
.gitignore
vendored
14
.gitignore
vendored
|
|
@ -1,5 +1,6 @@
|
|||
/tmp
|
||||
/.tmp
|
||||
/dist
|
||||
|
||||
# Created by https://www.gitignore.io/api/osx,macos,go
|
||||
|
||||
|
|
@ -9,6 +10,7 @@
|
|||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
/git-chglog
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
|
@ -19,17 +21,27 @@
|
|||
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||
.glide/
|
||||
|
||||
# in https://github.com/git-chglog/git-chglog/pull/85 we moved from dep to gomod
|
||||
vendor
|
||||
|
||||
### macOS ###
|
||||
*.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
Icon
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Intellij IDEA
|
||||
*.iml
|
||||
.idea
|
||||
|
||||
# vscode IDE
|
||||
.vscode
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
|
|
|
|||
103
.golangci.yml
Normal file
103
.golangci.yml
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
# https://golangci-lint.run/usage/configuration/
|
||||
run:
|
||||
timeout: 10m
|
||||
|
||||
tests: true
|
||||
|
||||
linters-settings:
|
||||
gofmt:
|
||||
# simplify code: gofmt with `-s` option, true by default
|
||||
simplify: true
|
||||
|
||||
goimports:
|
||||
# put imports beginning with prefix after 3rd-party packages;
|
||||
# it's a comma-separated list of prefixes
|
||||
local-prefixes: github.com/git-chglog/git-chglog
|
||||
|
||||
gocyclo:
|
||||
# minimal code complexity to report, 30 by default (but we recommend 10-20)
|
||||
min-complexity: 10
|
||||
|
||||
dupl:
|
||||
# tokens count to trigger issue, 150 by default
|
||||
threshold: 100
|
||||
|
||||
goconst:
|
||||
# minimal length of string constant, 3 by default
|
||||
min-len: 3
|
||||
# minimal occurrences count to trigger, 3 by default
|
||||
min-occurrences: 5
|
||||
|
||||
lll:
|
||||
# tab width in spaces. Default to 1.
|
||||
tab-width: 1
|
||||
|
||||
unparam:
|
||||
# Inspect exported functions, default is false. Set to true if no external program/library imports your code.
|
||||
# XXX: if you enable this setting, unparam will report a lot of false-positives in text editors:
|
||||
# if it's called for subdir of a project it can't find external interfaces. All text editor integrations
|
||||
# with golangci-lint call it on a directory with the changed file.
|
||||
check-exported: false
|
||||
|
||||
nakedret:
|
||||
# make an issue if func has more lines of code than this setting and it has naked returns; default is 30
|
||||
max-func-lines: 30
|
||||
|
||||
prealloc:
|
||||
# XXX: we don't recommend using this linter before doing performance profiling.
|
||||
# For most programs usage of prealloc will be a premature optimization.
|
||||
|
||||
# Report preallocation suggestions only on simple loops that have no returns/breaks/continues/gotos in them.
|
||||
# True by default.
|
||||
simple: true
|
||||
range-loops: true # Report preallocation suggestions on range loops, true by default
|
||||
for-loops: false # Report preallocation suggestions on for loops, false by default
|
||||
|
||||
misspell:
|
||||
locale: US
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- gosimple
|
||||
- staticcheck
|
||||
- unused
|
||||
- gocyclo
|
||||
- goconst
|
||||
- goimports
|
||||
- gofmt # We enable this as well as goimports for its simplify mode.
|
||||
- prealloc
|
||||
- unconvert
|
||||
- misspell
|
||||
- nakedret
|
||||
- dupl
|
||||
#- depguard TODO fix later
|
||||
|
||||
presets:
|
||||
- bugs
|
||||
- unused
|
||||
fast: false
|
||||
|
||||
issues:
|
||||
# Excluding configuration per-path and per-linter
|
||||
exclude-rules:
|
||||
# Exclude some linters from running on tests files.
|
||||
- path: _test(ing)?\.go
|
||||
linters:
|
||||
- gocyclo
|
||||
- errcheck
|
||||
- dupl
|
||||
- gosec
|
||||
- unparam
|
||||
- testifylint
|
||||
- depguard
|
||||
|
||||
# Show only new issues: if there are unstaged changes or untracked files,
|
||||
# only those changes are analyzed, else only changes in HEAD~ are analyzed.
|
||||
# It's a super-useful option for integration of golangci-lint into existing
|
||||
# large codebase. It's not practical to fix all existing issues at the moment
|
||||
# of integration: much better don't allow issues in new code.
|
||||
# Default is false.
|
||||
new: false
|
||||
|
||||
# Maximum count of issues with the same text. Set to 0 to disable. Default is 3.
|
||||
max-same-issues: 0
|
||||
57
.goreleaser.yml
Normal file
57
.goreleaser.yml
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
version: 2
|
||||
|
||||
# This is an example .goreleaser.yml file with some sane defaults.
|
||||
# Make sure to check the documentation at http://goreleaser.com
|
||||
project_name: git-chglog
|
||||
|
||||
before:
|
||||
hooks:
|
||||
# You may remove this if you don't use go modules.
|
||||
- go mod download
|
||||
|
||||
builds:
|
||||
- dir: cmd/git-chglog
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
- windows
|
||||
- darwin
|
||||
goarch:
|
||||
- 386
|
||||
- amd64
|
||||
- arm
|
||||
- arm64
|
||||
|
||||
archives:
|
||||
- format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
|
||||
checksum:
|
||||
name_template: "checksums.txt"
|
||||
|
||||
changelog:
|
||||
sort: desc
|
||||
filters:
|
||||
exclude:
|
||||
- "^Merge"
|
||||
|
||||
snapshot:
|
||||
name_template: "{{ .Tag }}-next"
|
||||
|
||||
dockers:
|
||||
- image_templates:
|
||||
- "quay.io/git-chglog/git-chglog:{{ .RawVersion }}"
|
||||
- "quay.io/git-chglog/git-chglog:latest"
|
||||
|
||||
brews:
|
||||
- repository:
|
||||
owner: git-chglog
|
||||
name: homebrew-git-chglog
|
||||
homepage: "https://godoc.org/github.com/git-chglog/git-chglog"
|
||||
description: "CHANGELOG generator implemented in Go (Golang)."
|
||||
test: |
|
||||
system "#{bin}/git-chglog --help"
|
||||
install: |
|
||||
bin.install "git-chglog"
|
||||
22
.travis.yml
22
.travis.yml
|
|
@ -1,22 +0,0 @@
|
|||
language: go
|
||||
sudo: false
|
||||
notifications:
|
||||
email: false
|
||||
go:
|
||||
- 1.9
|
||||
before_install:
|
||||
- go get github.com/mitchellh/gox
|
||||
- go get github.com/tcnksm/ghr
|
||||
before_script:
|
||||
- export TZ=Asia/Tokyo
|
||||
- sudo timedatectl set-timezone Asia/Tokyo
|
||||
- timedatectl
|
||||
script:
|
||||
- make test
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
after_success:
|
||||
- echo "TODO release process"
|
||||
# - gox -os "darwin linux windows" -arch "amd64" -output "dist/{{.OS}}_{{.Arch}}_{{.Dir}}" .
|
||||
# - ghr --username tsuyoshiwada --token $GITHUB_TOKEN --replace `grep 'Version =' version.go | sed -E 's/.*"(.+)"$$/\1/'` dist/
|
||||
461
CHANGELOG.md
461
CHANGELOG.md
|
|
@ -0,0 +1,461 @@
|
|||
<a name="unreleased"></a>
|
||||
## [Unreleased]
|
||||
|
||||
|
||||
<a name="v0.15.4"></a>
|
||||
## [v0.15.4] - 2023-02-15
|
||||
### Bug Fixes
|
||||
- release process ([#231](https://github.com/git-chglog/git-chglog/issues/231))
|
||||
- **ci:** add integration test with docker image ([#226](https://github.com/git-chglog/git-chglog/issues/226))
|
||||
- **deps:** update module github.com/urfave/cli/v2 to v2.24.3 ([#227](https://github.com/git-chglog/git-chglog/issues/227))
|
||||
- **deps:** update module github.com/fatih/color to v1.14.1 ([#224](https://github.com/git-chglog/git-chglog/issues/224))
|
||||
- **deps:** update all non-major dependencies ([#223](https://github.com/git-chglog/git-chglog/issues/223))
|
||||
|
||||
### Chores
|
||||
- new release due to shecksum mismatch. Closes [#232](https://github.com/git-chglog/git-chglog/issues/232)
|
||||
- update changelog for v0.15.3
|
||||
- update changelog for v0.15.3
|
||||
- bump docker/login-action to v2. Closes [#206](https://github.com/git-chglog/git-chglog/issues/206)
|
||||
- bump goreleaser/goreleaser-action to v4. Closes [#208](https://github.com/git-chglog/git-chglog/issues/208)
|
||||
|
||||
|
||||
<a name="v0.15.2"></a>
|
||||
## [v0.15.2] - 2023-01-22
|
||||
### Bug Fixes
|
||||
- typo in readme ([#196](https://github.com/git-chglog/git-chglog/issues/196))
|
||||
- **deps:** update module gopkg.in/yaml.v2 to v3 ([#221](https://github.com/git-chglog/git-chglog/issues/221))
|
||||
|
||||
### Chores
|
||||
- update changelog for v0.15.2
|
||||
- update changelog for v0.15.2
|
||||
- update Go install instructions ([#205](https://github.com/git-chglog/git-chglog/issues/205))
|
||||
- bump golang to 1.19 ([#218](https://github.com/git-chglog/git-chglog/issues/218))
|
||||
- **deps:** update all non-major dependencies ([#220](https://github.com/git-chglog/git-chglog/issues/220))
|
||||
|
||||
|
||||
<a name="v0.15.1"></a>
|
||||
## [v0.15.1] - 2021-12-14
|
||||
### Bug Fixes
|
||||
- **deps:** update all non-major dependencies ([#179](https://github.com/git-chglog/git-chglog/issues/179))
|
||||
- **deps:** update module github.com/andygrunwald/go-jira to v1.14.0 ([#167](https://github.com/git-chglog/git-chglog/issues/167))
|
||||
- **deps:** update module github.com/alecaivazis/survey/v2 to v2.2.16 ([#165](https://github.com/git-chglog/git-chglog/issues/165))
|
||||
- **deps:** update module github.com/alecaivazis/survey/v2 to v2.2.15 ([#163](https://github.com/git-chglog/git-chglog/issues/163))
|
||||
|
||||
### Chores
|
||||
- update changelog for v0.15.1
|
||||
- ignore .vscode
|
||||
- **deps:** update all non-major dependencies docker tags ([#168](https://github.com/git-chglog/git-chglog/issues/168))
|
||||
|
||||
|
||||
<a name="v0.15.0"></a>
|
||||
## [v0.15.0] - 2021-07-09
|
||||
### Bug Fixes
|
||||
- **deps:** update module github.com/alecaivazis/survey/v2 to v2.2.14 ([#158](https://github.com/git-chglog/git-chglog/issues/158))
|
||||
- **deps:** update module github.com/alecaivazis/survey/v2 to v2.2.13 ([#156](https://github.com/git-chglog/git-chglog/issues/156))
|
||||
- **deps:** update module github.com/fatih/color to v1.12.0 ([#150](https://github.com/git-chglog/git-chglog/issues/150))
|
||||
- **deps:** update module github.com/fatih/color to v1.11.0 ([#149](https://github.com/git-chglog/git-chglog/issues/149))
|
||||
- **deps:** update module github.com/alecaivazis/survey/v2 to v2.2.12 ([#147](https://github.com/git-chglog/git-chglog/issues/147))
|
||||
|
||||
### Chores
|
||||
- update changelog for v0.15.0
|
||||
- **deps:** update alpine docker tag to v3.14.0 ([#153](https://github.com/git-chglog/git-chglog/issues/153))
|
||||
|
||||
### Features
|
||||
- Automatic link for gitlab merge requests ([#160](https://github.com/git-chglog/git-chglog/issues/160))
|
||||
|
||||
|
||||
<a name="v0.14.2"></a>
|
||||
## [v0.14.2] - 2021-04-16
|
||||
### Bug Fixes
|
||||
- add CommitGroupTitleOrder back to Options ([#143](https://github.com/git-chglog/git-chglog/issues/143))
|
||||
|
||||
### Chores
|
||||
- update changelog for v0.14.2
|
||||
- **deps:** update alpine docker tag to v3.13.5 ([#144](https://github.com/git-chglog/git-chglog/issues/144))
|
||||
|
||||
|
||||
<a name="v0.14.1"></a>
|
||||
## [v0.14.1] - 2021-04-13
|
||||
### Bug Fixes
|
||||
- **template:** address regression in string functions for template engine ([#142](https://github.com/git-chglog/git-chglog/issues/142))
|
||||
|
||||
### Chores
|
||||
- update changelog for v0.14.1
|
||||
- add docker target to Makefile ([#138](https://github.com/git-chglog/git-chglog/issues/138))
|
||||
- add make release target ([#130](https://github.com/git-chglog/git-chglog/issues/130))
|
||||
- **deps:** update alpine docker tag to v3.13.4 ([#136](https://github.com/git-chglog/git-chglog/issues/136))
|
||||
|
||||
### Features
|
||||
- add docker image on release and master ([#135](https://github.com/git-chglog/git-chglog/issues/135))
|
||||
|
||||
|
||||
<a name="v0.14.0"></a>
|
||||
## [v0.14.0] - 2021-03-28
|
||||
### Chores
|
||||
- update changelog for v0.14.0
|
||||
- **CHANGELOG:** regenerate CHANGELOG with type-scope and KAC template ([#129](https://github.com/git-chglog/git-chglog/issues/129))
|
||||
|
||||
### Features
|
||||
- add sprig template functions support ([#131](https://github.com/git-chglog/git-chglog/issues/131))
|
||||
- add `--sort [TYPE]` flag ([#78](https://github.com/git-chglog/git-chglog/issues/78))
|
||||
|
||||
|
||||
<a name="v0.13.0"></a>
|
||||
## [v0.13.0] - 2021-03-23
|
||||
### Chores
|
||||
- update changelog for v0.13.0
|
||||
- use ldflags to pass version to build process ([#127](https://github.com/git-chglog/git-chglog/issues/127))
|
||||
|
||||
### Features
|
||||
- add support for rendering .Body after .Subject as part of list ([#121](https://github.com/git-chglog/git-chglog/issues/121))
|
||||
|
||||
|
||||
<a name="v0.12.0"></a>
|
||||
## [v0.12.0] - 2021-03-20
|
||||
### Chores
|
||||
- update changelog for v0.12.0
|
||||
- bumps version to v0.12.0
|
||||
- bump golang to 1.16 ([#118](https://github.com/git-chglog/git-chglog/issues/118))
|
||||
- **ci:** add golangci-lint action and apply linting changes ([#120](https://github.com/git-chglog/git-chglog/issues/120))
|
||||
|
||||
### Features
|
||||
- allow tag sorting by semver ([#124](https://github.com/git-chglog/git-chglog/issues/124))
|
||||
|
||||
### BREAKING CHANGE
|
||||
|
||||
`JiraIssueId` has been renamed to `JiraIssueID`. This impacts the value for `pattern_maps` in `config.yml`.
|
||||
|
||||
* chore(ci): add golangci-lint action
|
||||
|
||||
* chore(lint): address errcheck lint failures
|
||||
|
||||
* chore(lint): address misspell lint failures
|
||||
|
||||
* chore(lint): address gocritic lint failures
|
||||
|
||||
* chore(lint): address golint lint failures
|
||||
|
||||
* chore(lint): address structcheck lint failures
|
||||
|
||||
* chore(lint): address gosimple lint failures
|
||||
|
||||
* chore(lint): address gofmt lint failures
|
||||
|
||||
* chore(ci): port to official golangci-lint github action
|
||||
|
||||
* Update golangci configuration for better coverage
|
||||
|
||||
|
||||
<a name="v0.11.2"></a>
|
||||
## [v0.11.2] - 2021-03-13
|
||||
### Bug Fixes
|
||||
- `--template` and `--repository-url` flags not being used ([#119](https://github.com/git-chglog/git-chglog/issues/119))
|
||||
|
||||
### Chores
|
||||
- update changelog for v0.11.2
|
||||
- bumps version to v0.11.2
|
||||
|
||||
|
||||
<a name="v0.11.1"></a>
|
||||
## [v0.11.1] - 2021-03-12
|
||||
### Bug Fixes
|
||||
- **short flags:** correctly define cli flags with shorthands ([#117](https://github.com/git-chglog/git-chglog/issues/117))
|
||||
|
||||
### Chores
|
||||
- update readme and changelog for v0.11.1
|
||||
- bumps version to v0.11.1
|
||||
|
||||
|
||||
<a name="v0.11.0"></a>
|
||||
## [v0.11.0] - 2021-03-12
|
||||
### Bug Fixes
|
||||
- **deps:** update all non-major dependencies ([#115](https://github.com/git-chglog/git-chglog/issues/115))
|
||||
- **deps:** update module gopkg.in/kyokomi/emoji.v1 to github.com/kyokomi/emoji/v2 ([#109](https://github.com/git-chglog/git-chglog/issues/109))
|
||||
- **deps:** update module github.com/urfave/cli to v2 ([#107](https://github.com/git-chglog/git-chglog/issues/107))
|
||||
- **deps:** update module github.com/stretchr/testify to v1.7.0 ([#103](https://github.com/git-chglog/git-chglog/issues/103))
|
||||
- **deps:** update module gopkg.in/alecaivazis/survey.v1 to github.com/AlecAivazis/survey/v2 ([#108](https://github.com/git-chglog/git-chglog/issues/108))
|
||||
- **init:** support OptionAnswer form in survey/v2 ([#113](https://github.com/git-chglog/git-chglog/issues/113))
|
||||
|
||||
### Chores
|
||||
- update changelog for v0.11.0
|
||||
- bumps version to v0.11.0
|
||||
- **deps:** add initial renovatebot configuration ([#102](https://github.com/git-chglog/git-chglog/issues/102))
|
||||
|
||||
### Features
|
||||
- add Jira integration ([#52](https://github.com/git-chglog/git-chglog/issues/52))
|
||||
- **flag:** --path filtering - refs ([#62](https://github.com/git-chglog/git-chglog/issues/62)). Closes [#35](https://github.com/git-chglog/git-chglog/issues/35)
|
||||
|
||||
|
||||
<a name="v0.10.0"></a>
|
||||
## [v0.10.0] - 2021-01-16
|
||||
### Bug Fixes
|
||||
- ignore only git-chglog binary in root and not subfolder
|
||||
|
||||
### Chores
|
||||
- update changelog for v0.10.0
|
||||
- bumps version to v0.10.0
|
||||
- sorts changelog desc and excludes Merge commits
|
||||
- fix Makefile typo ([#82](https://github.com/git-chglog/git-chglog/issues/82))
|
||||
- **asdf:** add asdf install support to README ([#79](https://github.com/git-chglog/git-chglog/issues/79))
|
||||
|
||||
### Features
|
||||
- Adds 'Custom' sort_type to CommitGroup ([#69](https://github.com/git-chglog/git-chglog/issues/69))
|
||||
- enable tag_filter_pattern in config options ([#72](https://github.com/git-chglog/git-chglog/issues/72))
|
||||
- switch from dep to go mod ([#85](https://github.com/git-chglog/git-chglog/issues/85))
|
||||
- add option to filter commits in a case insensitive way
|
||||
- add upperFirst template function
|
||||
- Add emoji format and some formatters in variables
|
||||
|
||||
### Reverts
|
||||
- Revert "ci: switches to personal GH Token for brew cross repo releases"
|
||||
- ci: switches to personal GH Token for brew cross repo releases
|
||||
|
||||
### Pull Requests
|
||||
- Merge pull request [#65](https://github.com/git-chglog/git-chglog/issues/65) from barryib/case-sensitive-option
|
||||
- Merge pull request [#59](https://github.com/git-chglog/git-chglog/issues/59) from momotaro98/feature/add-emoji-template-in-init
|
||||
- Merge pull request [#66](https://github.com/git-chglog/git-chglog/issues/66) from barryib/add-upper-first-func
|
||||
- Merge pull request [#68](https://github.com/git-chglog/git-chglog/issues/68) from unixorn/tweak-readme
|
||||
|
||||
|
||||
<a name="0.9.1"></a>
|
||||
## [0.9.1] - 2019-09-23
|
||||
|
||||
<a name="0.9.0"></a>
|
||||
## [0.9.0] - 2019-09-23
|
||||
### Bug Fixes
|
||||
- Fixing tests on windows
|
||||
|
||||
### Features
|
||||
- Add --tag-filter-pattern flag.
|
||||
|
||||
### Pull Requests
|
||||
- Merge pull request [#44](https://github.com/git-chglog/git-chglog/issues/44) from evanchaoli/tag-filter
|
||||
- Merge pull request [#41](https://github.com/git-chglog/git-chglog/issues/41) from StanleyGoldman/fixing-tests-windows
|
||||
- Merge pull request [#37](https://github.com/git-chglog/git-chglog/issues/37) from ForkingSyndrome/master
|
||||
|
||||
|
||||
<a name="0.8.0"></a>
|
||||
## [0.8.0] - 2019-02-23
|
||||
### Features
|
||||
- add the contains, hasPrefix, hasSuffix, replace, lower and upper functions to the template functions map
|
||||
|
||||
### Pull Requests
|
||||
- Merge pull request [#34](https://github.com/git-chglog/git-chglog/issues/34) from atosatto/template-functions
|
||||
|
||||
|
||||
<a name="0.7.1"></a>
|
||||
## [0.7.1] - 2018-11-10
|
||||
### Bug Fixes
|
||||
- Panic occured when exec --next-tag with HEAD with tag
|
||||
|
||||
### Pull Requests
|
||||
- Merge pull request [#31](https://github.com/git-chglog/git-chglog/issues/31) from drubin/patch-1
|
||||
- Merge pull request [#30](https://github.com/git-chglog/git-chglog/issues/30) from vvakame/fix-panic
|
||||
|
||||
|
||||
<a name="0.7.0"></a>
|
||||
## [0.7.0] - 2018-05-06
|
||||
### Bug Fixes
|
||||
- Remove accidentally added `Unreleased.Tag`
|
||||
|
||||
### Chores
|
||||
- Update `changelog` task in Makefile
|
||||
|
||||
### Features
|
||||
- Add URL of output example for template style
|
||||
- Add `--next-tag` flag (experimental)
|
||||
|
||||
### Pull Requests
|
||||
- Merge pull request [#22](https://github.com/git-chglog/git-chglog/issues/22) from git-chglog/feat/add-preview-style-link
|
||||
- Merge pull request [#21](https://github.com/git-chglog/git-chglog/issues/21) from git-chglog/feat/next-tag
|
||||
|
||||
|
||||
<a name="0.6.0"></a>
|
||||
## [0.6.0] - 2018-05-04
|
||||
### Chores
|
||||
- Update CHANGELOG template format
|
||||
|
||||
### Features
|
||||
- Add tag name header id for keep-a-changelog template
|
||||
|
||||
### Pull Requests
|
||||
- Merge pull request [#20](https://github.com/git-chglog/git-chglog/issues/20) from git-chglog/feat/kac-template-title-id
|
||||
|
||||
|
||||
<a name="0.5.0"></a>
|
||||
## [0.5.0] - 2018-05-04
|
||||
### Bug Fixes
|
||||
- Add unreleased commits section to keep-a-changelog template [#15](https://github.com/git-chglog/git-chglog/issues/15)
|
||||
|
||||
### Chores
|
||||
- Update CHANGELOG template format
|
||||
|
||||
### Features
|
||||
- Update template format to human readable
|
||||
- Add `Unreleased` field to `RenderData`
|
||||
|
||||
### Pull Requests
|
||||
- Merge pull request [#19](https://github.com/git-chglog/git-chglog/issues/19) from git-chglog/fix/unreleased-commits
|
||||
- Merge pull request [#18](https://github.com/git-chglog/git-chglog/issues/18) from ringohub/master
|
||||
|
||||
|
||||
<a name="0.4.0"></a>
|
||||
## [0.4.0] - 2018-04-14
|
||||
### Features
|
||||
- Add support for Bitbucket
|
||||
|
||||
### Pull Requests
|
||||
- Merge pull request [#17](https://github.com/git-chglog/git-chglog/issues/17) from git-chglog/feat/bitbucket
|
||||
|
||||
|
||||
<a name="0.3.3"></a>
|
||||
## [0.3.3] - 2018-04-07
|
||||
### Features
|
||||
- Change to kindly error message when git-tag does not exist
|
||||
|
||||
### Pull Requests
|
||||
- Merge pull request [#16](https://github.com/git-chglog/git-chglog/issues/16) from git-chglog/fix/empty-tag-handling
|
||||
|
||||
|
||||
<a name="0.3.2"></a>
|
||||
## [0.3.2] - 2018-04-02
|
||||
### Bug Fixes
|
||||
- Fix color output bug in windows help command
|
||||
|
||||
### Pull Requests
|
||||
- Merge pull request [#14](https://github.com/git-chglog/git-chglog/issues/14) from git-chglog/fix/windows-help-color
|
||||
|
||||
|
||||
<a name="0.3.1"></a>
|
||||
## [0.3.1] - 2018-03-15
|
||||
### Bug Fixes
|
||||
- fix preview string of commit subject
|
||||
|
||||
### Pull Requests
|
||||
- Merge pull request [#13](https://github.com/git-chglog/git-chglog/issues/13) from kt3k/feature/fix-preview
|
||||
|
||||
|
||||
<a name="0.3.0"></a>
|
||||
## [0.3.0] - 2018-03-12
|
||||
### Chores
|
||||
- Add helper task for generate CHANGELOG
|
||||
|
||||
### Features
|
||||
- Add support for GitLab
|
||||
|
||||
### Pull Requests
|
||||
- Merge pull request [#12](https://github.com/git-chglog/git-chglog/issues/12) from git-chglog/feat/gitlab
|
||||
|
||||
|
||||
<a name="0.2.0"></a>
|
||||
## [0.2.0] - 2018-03-02
|
||||
### Chores
|
||||
- Fix release flow (retry)
|
||||
- Add AppVeyor config
|
||||
|
||||
### Features
|
||||
- Add template for `Keep a changelog` to the `--init` option
|
||||
- Supports vim like `j/k` keybind with item selection of `--init`
|
||||
|
||||
### Pull Requests
|
||||
- Merge pull request [#11](https://github.com/git-chglog/git-chglog/issues/11) from git-chglog/develop
|
||||
- Merge pull request [#10](https://github.com/git-chglog/git-chglog/issues/10) from mattn/fix-test
|
||||
- Merge pull request [#9](https://github.com/git-chglog/git-chglog/issues/9) from mattn/windows-color
|
||||
|
||||
|
||||
<a name="0.1.0"></a>
|
||||
## [0.1.0] - 2018-02-25
|
||||
### Bug Fixes
|
||||
- Fix a bug that `Commit.Revert.Header` is not converted by `GitHubProcessor`
|
||||
- Fix error message when `Tag` can not be acquired
|
||||
- Fix `Revert` of template created by Initializer
|
||||
|
||||
### Chores
|
||||
- Fix release scripts
|
||||
- Remove unnecessary task
|
||||
- Add coverage measurement task for local confirmation
|
||||
- Change release method of git tag on TravisCI
|
||||
|
||||
### Code Refactoring
|
||||
- Refactor `Initializer` to testable
|
||||
|
||||
### Features
|
||||
- Supports annotated git-tag and adds `Tag.Subject` field [#3](https://github.com/git-chglog/git-chglog/issues/3)
|
||||
- Remove commit message preview on select format
|
||||
- Add Git Basic to commit message format
|
||||
- Add preview to the commit message format of `--init` option
|
||||
|
||||
### Pull Requests
|
||||
- Merge pull request [#8](https://github.com/git-chglog/git-chglog/issues/8) from git-chglog/feat/0.0.3
|
||||
- Merge pull request [#6](https://github.com/git-chglog/git-chglog/issues/6) from git-chglog/chore/coverage
|
||||
- Merge pull request [#4](https://github.com/git-chglog/git-chglog/issues/4) from paralax/patch-1
|
||||
- Merge pull request [#5](https://github.com/git-chglog/git-chglog/issues/5) from git-chglog/develop
|
||||
- Merge pull request [#1](https://github.com/git-chglog/git-chglog/issues/1) from git-chglog/develop
|
||||
|
||||
|
||||
<a name="0.0.2"></a>
|
||||
## [0.0.2] - 2018-02-18
|
||||
### Chores
|
||||
- Fix release script
|
||||
- Add release process
|
||||
|
||||
|
||||
<a name="0.0.1"></a>
|
||||
## 0.0.1 - 2018-02-18
|
||||
### Bug Fixes
|
||||
- Fix parsing of revert and body
|
||||
|
||||
### Chores
|
||||
- Fix timezone in TravisCI
|
||||
- Add travis configuration
|
||||
- Add Makefile for task management
|
||||
- Fix testcase depending on datetime
|
||||
- Update vendor packages
|
||||
- Add e2e tests
|
||||
- Setup gitignore
|
||||
- Initial commit
|
||||
- **editor:** Add Editorconfig
|
||||
|
||||
### Code Refactoring
|
||||
- Fix typo
|
||||
- Change to return an error if corresponding commit is empty
|
||||
- Refactor the main logic
|
||||
|
||||
### Features
|
||||
- Add cli client
|
||||
- Add commits in commit version struct
|
||||
- Add config normalize process
|
||||
- Add Next and Previous in Tag
|
||||
- Add MergeCommits and RevertCommits
|
||||
- First implement
|
||||
|
||||
|
||||
[Unreleased]: https://github.com/git-chglog/git-chglog/compare/v0.15.4...HEAD
|
||||
[v0.15.4]: https://github.com/git-chglog/git-chglog/compare/v0.15.2...v0.15.4
|
||||
[v0.15.2]: https://github.com/git-chglog/git-chglog/compare/v0.15.1...v0.15.2
|
||||
[v0.15.1]: https://github.com/git-chglog/git-chglog/compare/v0.15.0...v0.15.1
|
||||
[v0.15.0]: https://github.com/git-chglog/git-chglog/compare/v0.14.2...v0.15.0
|
||||
[v0.14.2]: https://github.com/git-chglog/git-chglog/compare/v0.14.1...v0.14.2
|
||||
[v0.14.1]: https://github.com/git-chglog/git-chglog/compare/v0.14.0...v0.14.1
|
||||
[v0.14.0]: https://github.com/git-chglog/git-chglog/compare/v0.13.0...v0.14.0
|
||||
[v0.13.0]: https://github.com/git-chglog/git-chglog/compare/v0.12.0...v0.13.0
|
||||
[v0.12.0]: https://github.com/git-chglog/git-chglog/compare/v0.11.2...v0.12.0
|
||||
[v0.11.2]: https://github.com/git-chglog/git-chglog/compare/v0.11.1...v0.11.2
|
||||
[v0.11.1]: https://github.com/git-chglog/git-chglog/compare/v0.11.0...v0.11.1
|
||||
[v0.11.0]: https://github.com/git-chglog/git-chglog/compare/v0.10.0...v0.11.0
|
||||
[v0.10.0]: https://github.com/git-chglog/git-chglog/compare/0.9.1...v0.10.0
|
||||
[0.9.1]: https://github.com/git-chglog/git-chglog/compare/0.9.0...0.9.1
|
||||
[0.9.0]: https://github.com/git-chglog/git-chglog/compare/0.8.0...0.9.0
|
||||
[0.8.0]: https://github.com/git-chglog/git-chglog/compare/0.7.1...0.8.0
|
||||
[0.7.1]: https://github.com/git-chglog/git-chglog/compare/0.7.0...0.7.1
|
||||
[0.7.0]: https://github.com/git-chglog/git-chglog/compare/0.6.0...0.7.0
|
||||
[0.6.0]: https://github.com/git-chglog/git-chglog/compare/0.5.0...0.6.0
|
||||
[0.5.0]: https://github.com/git-chglog/git-chglog/compare/0.4.0...0.5.0
|
||||
[0.4.0]: https://github.com/git-chglog/git-chglog/compare/0.3.3...0.4.0
|
||||
[0.3.3]: https://github.com/git-chglog/git-chglog/compare/0.3.2...0.3.3
|
||||
[0.3.2]: https://github.com/git-chglog/git-chglog/compare/0.3.1...0.3.2
|
||||
[0.3.1]: https://github.com/git-chglog/git-chglog/compare/0.3.0...0.3.1
|
||||
[0.3.0]: https://github.com/git-chglog/git-chglog/compare/0.2.0...0.3.0
|
||||
[0.2.0]: https://github.com/git-chglog/git-chglog/compare/0.1.0...0.2.0
|
||||
[0.1.0]: https://github.com/git-chglog/git-chglog/compare/0.0.2...0.1.0
|
||||
[0.0.2]: https://github.com/git-chglog/git-chglog/compare/0.0.1...0.0.2
|
||||
54
CONTRIBUTING.md
Normal file
54
CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# Contributing `git-chglog`
|
||||
|
||||
Thank you for contributing `git-chglog` :tada:
|
||||
|
||||
## Templates
|
||||
|
||||
Please use issue/PR templates which are inserted automatically.
|
||||
|
||||
## Found a Bug?
|
||||
|
||||
If you find a bug in the source code, you can help us by [submitting an issue]
|
||||
to our [GitHub Repository]. Even better, you can submit a Pull Request with a
|
||||
fix.
|
||||
|
||||
## Commit Message Format
|
||||
|
||||
A format influenced by [Angular commit message].
|
||||
|
||||
```text
|
||||
<type>: <subject>
|
||||
<BLANK LINE>
|
||||
<body>
|
||||
<BLANK LINE>
|
||||
<footer>
|
||||
```
|
||||
|
||||
### Type
|
||||
|
||||
Must be one of the following:
|
||||
|
||||
- **docs:** Documention only changes
|
||||
- **ci:** Changes to our CI configuration files and scripts
|
||||
- **chore:** Updating Makefile etc, no production code changes
|
||||
- **feat:** A new feature
|
||||
- **fix:** A bug fix
|
||||
- **perf:** A code change that improves performance
|
||||
- **refactor:** A code change that neither fixes a bug nor adds a feature
|
||||
- **style:** Changes that do not affect the meaning of the code
|
||||
- **test:** Adding missing tests or correcting existing tests
|
||||
|
||||
### Footer
|
||||
|
||||
The footer should contain a [closing reference to an issue] if any.
|
||||
|
||||
The **footer** should contain any information about **Breaking Changes** and is
|
||||
also the place to reference GitHub issues that this commit **Closes**.
|
||||
|
||||
**Breaking Changes** should start with the word `BREAKING CHANGE:` with a space
|
||||
or two newlines. The rest of the commit message is then used for this.
|
||||
|
||||
[submitting an issue]: https://github.com/git-chglog/git-chglog/issues
|
||||
[GitHub Repository]: https://github.com/git-chglog/git-chglog
|
||||
[Angular commit message]: https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit-message-format
|
||||
[closing reference to an issue]: https://help.github.com/articles/closing-issues-via-commit-messages/
|
||||
12
Dockerfile
Normal file
12
Dockerfile
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
FROM alpine
|
||||
|
||||
RUN apk add --no-cache git && \
|
||||
mkdir /workdir && \
|
||||
git config --global --add safe.directory /workdir
|
||||
|
||||
COPY git-chglog /usr/local/bin/git-chglog
|
||||
|
||||
WORKDIR /workdir
|
||||
RUN chmod +x /usr/local/bin/git-chglog
|
||||
|
||||
ENTRYPOINT [ "/usr/local/bin/git-chglog" ]
|
||||
97
Gopkg.lock
generated
97
Gopkg.lock
generated
|
|
@ -1,97 +0,0 @@
|
|||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/davecgh/go-spew"
|
||||
packages = ["spew"]
|
||||
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/fatih/color"
|
||||
packages = ["."]
|
||||
revision = "507f6050b8568533fb3f5504de8e5205fa62a114"
|
||||
version = "v1.6.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/imdario/mergo"
|
||||
packages = ["."]
|
||||
revision = "163f41321a19dd09362d4c63cc2489db2015f1f4"
|
||||
version = "0.3.2"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/mattn/go-colorable"
|
||||
packages = ["."]
|
||||
revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072"
|
||||
version = "v0.0.9"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/mattn/go-isatty"
|
||||
packages = ["."]
|
||||
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
|
||||
version = "v0.0.3"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/mgutz/ansi"
|
||||
packages = ["."]
|
||||
revision = "9520e82c474b0a04dd04f8a40959027271bab992"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/pmezard/go-difflib"
|
||||
packages = ["difflib"]
|
||||
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/stretchr/testify"
|
||||
packages = ["assert"]
|
||||
revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71"
|
||||
version = "v1.2.1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/tsuyoshiwada/go-gitcmd"
|
||||
packages = ["."]
|
||||
revision = "5f1f5f9475df211f8b48620d704284d7375229c3"
|
||||
version = "0.0.1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/urfave/cli"
|
||||
packages = ["."]
|
||||
revision = "cfb38830724cc34fedffe9a2a29fb54fa9169cd1"
|
||||
version = "v1.20.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/sys"
|
||||
packages = ["unix"]
|
||||
revision = "37707fdb30a5b38865cfb95e5aab41707daec7fd"
|
||||
|
||||
[[projects]]
|
||||
name = "gopkg.in/AlecAivazis/survey.v1"
|
||||
packages = [
|
||||
".",
|
||||
"core",
|
||||
"terminal"
|
||||
]
|
||||
revision = "0aa8b6a162b391fe2d95648b7677d1d6ac2090a6"
|
||||
version = "v1.4.1"
|
||||
|
||||
[[projects]]
|
||||
name = "gopkg.in/kyokomi/emoji.v1"
|
||||
packages = ["."]
|
||||
revision = "7e06b236c489543f53868841f188a294e3383eab"
|
||||
version = "v1.5"
|
||||
|
||||
[[projects]]
|
||||
branch = "v2"
|
||||
name = "gopkg.in/yaml.v2"
|
||||
packages = ["."]
|
||||
revision = "d670f9405373e636a5a2765eea47fac0c9bc91a4"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "0be1d1c2f1a9b1c1f1d63ad065141425b62687efb378f6657b088ddf993c3882"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
36
Gopkg.toml
36
Gopkg.toml
|
|
@ -1,36 +0,0 @@
|
|||
[[constraint]]
|
||||
name = "github.com/stretchr/testify"
|
||||
version = "1.2.1"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/tsuyoshiwada/go-gitcmd"
|
||||
version = "0.0.1"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/urfave/cli"
|
||||
version = "1.20.0"
|
||||
|
||||
[prune]
|
||||
non-go = true
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/fatih/color"
|
||||
version = "1.6.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/imdario/mergo"
|
||||
version = "0.3.2"
|
||||
|
||||
[[constraint]]
|
||||
branch = "v2"
|
||||
name = "gopkg.in/yaml.v2"
|
||||
|
||||
[[constraint]]
|
||||
name = "gopkg.in/AlecAivazis/survey.v1"
|
||||
version = "1.4.1"
|
||||
|
||||
[[constraint]]
|
||||
name = "gopkg.in/kyokomi/emoji.v1"
|
||||
version = "1.5.0"
|
||||
116
Makefile
116
Makefile
|
|
@ -1,28 +1,108 @@
|
|||
.PHONY: bootstrap
|
||||
bootstrap: clean deps
|
||||
# Build variables
|
||||
VERSION ?= $(shell git describe --tags --always)
|
||||
|
||||
.PHONY: deps
|
||||
deps:
|
||||
dep ensure -v
|
||||
# Go variables
|
||||
GO ?= go
|
||||
GOOS ?= $(shell $(GO) env GOOS)
|
||||
GOARCH ?= $(shell $(GO) env GOARCH)
|
||||
GOHOST ?= GOOS=$(GOOS) GOARCH=$(GOARCH) $(GO)
|
||||
|
||||
LDFLAGS ?= "-X main.version=$(VERSION)"
|
||||
|
||||
# Docker variables
|
||||
DEFAULT_TAG ?= $(shell echo "$(VERSION)" | tr -d 'v')
|
||||
DOCKER_IMAGE := quay.io/git-chglog/git-chglog
|
||||
DOCKER_TAG ?= $(DEFAULT_TAG)
|
||||
|
||||
.PHONY: all
|
||||
all: help
|
||||
|
||||
###############
|
||||
##@ Development
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf ./vendor/
|
||||
clean: ## Clean workspace
|
||||
@ $(MAKE) --no-print-directory log-$@
|
||||
rm -rf ./dist/
|
||||
rm -rf ./git-chglog
|
||||
rm -rf $(GOPATH)/bin/git-chglog
|
||||
|
||||
.PHONY: bulid
|
||||
build:
|
||||
go build -i -o git-chglog
|
||||
rm -rf cover.out
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
go test -v `go list ./... | grep -v /vendor/`
|
||||
test: ## Run tests
|
||||
@ $(MAKE) --no-print-directory log-$@
|
||||
$(GOHOST) test -covermode atomic -coverprofile cover.out -v ./...
|
||||
|
||||
.PHONY: lint
|
||||
lint: ## Run linters
|
||||
@ $(MAKE) --no-print-directory log-$@
|
||||
golangci-lint run
|
||||
|
||||
#########
|
||||
##@ Build
|
||||
|
||||
.PHONY: build
|
||||
build: ## Build git-chglog
|
||||
@ $(MAKE) --no-print-directory log-$@
|
||||
CGO_ENABLED=0 $(GOHOST) build -ldflags=$(LDFLAGS) -o git-chglog ./cmd/git-chglog
|
||||
|
||||
.PHONY: install
|
||||
install:
|
||||
go install ./cmd/git-chglog
|
||||
install: ## Install git-chglog
|
||||
@ $(MAKE) --no-print-directory log-$@
|
||||
$(GOHOST) install ./cmd/git-chglog
|
||||
|
||||
.PHONY: chglog
|
||||
chglog:
|
||||
git-chglog -c ./.chglog/config.yml
|
||||
.PHONY: docker
|
||||
docker: build ## Build Docker image
|
||||
@ $(MAKE) --no-print-directory log-$@
|
||||
docker build --pull --tag $(DOCKER_IMAGE):$(DOCKER_TAG) .
|
||||
|
||||
.PHONY: push
|
||||
push: ## Push Docker image
|
||||
@ $(MAKE) --no-print-directory log-$@
|
||||
docker push $(DOCKER_IMAGE):$(DOCKER_TAG)
|
||||
|
||||
###########
|
||||
##@ Release
|
||||
|
||||
.PHONY: changelog
|
||||
changelog: build ## Generate changelog
|
||||
@ $(MAKE) --no-print-directory log-$@
|
||||
./git-chglog --next-tag $(VERSION) -o CHANGELOG.md
|
||||
|
||||
.PHONY: release
|
||||
release: changelog ## Release a new tag
|
||||
@ $(MAKE) --no-print-directory log-$@
|
||||
git add CHANGELOG.md
|
||||
git commit -m "chore: update changelog for $(VERSION)"
|
||||
git tag $(VERSION)
|
||||
git push origin master $(VERSION)
|
||||
|
||||
########
|
||||
##@ Help
|
||||
|
||||
.PHONY: help
|
||||
help: ## Display this help
|
||||
@awk \
|
||||
-v "col=\033[36m" -v "nocol=\033[0m" \
|
||||
' \
|
||||
BEGIN { \
|
||||
FS = ":.*##" ; \
|
||||
printf "Usage:\n make %s<target>%s\n", col, nocol \
|
||||
} \
|
||||
/^[a-zA-Z_-]+:.*?##/ { \
|
||||
printf " %s%-12s%s %s\n", col, $$1, nocol, $$2 \
|
||||
} \
|
||||
/^##@/ { \
|
||||
printf "\n%s%s%s\n", nocol, substr($$0, 5), nocol \
|
||||
} \
|
||||
' $(MAKEFILE_LIST)
|
||||
|
||||
log-%:
|
||||
@grep -h -E '^$*:.*?## .*$$' $(MAKEFILE_LIST) | \
|
||||
awk \
|
||||
'BEGIN { \
|
||||
FS = ":.*?## " \
|
||||
}; \
|
||||
{ \
|
||||
printf "\033[36m==> %s\033[0m\n", $$2 \
|
||||
}'
|
||||
|
|
|
|||
583
README.md
583
README.md
|
|
@ -3,116 +3,153 @@
|
|||

|
||||
|
||||
[](https://godoc.org/github.com/git-chglog/git-chglog)
|
||||
[](https://travis-ci.org/git-chglog/git-chglog)
|
||||
[](https://github.com/git-chglog/git-chglog/actions)
|
||||
[](https://coveralls.io/github/git-chglog/git-chglog?branch=master)
|
||||
[](https://github.com/git-chglog/git-chglog/blob/master/LICENSE)
|
||||
|
||||
> CHANGELOG generator implemented in Go (Golang).
|
||||
> CHANGELOG generator implemented in Go (Golang).
|
||||
> _Anytime, anywhere, Write your CHANGELOG._
|
||||
|
||||
|
||||
|
||||
|
||||
## Table of Contents
|
||||
|
||||
* [Features](#features)
|
||||
* [How it works](#how-it-works)
|
||||
* [Getting Started](#getting-started)
|
||||
- [git-chglog](#git-chglog)
|
||||
- [Table of Contents](#table-of-contents)
|
||||
- [Features](#features)
|
||||
- [How it works](#how-it-works)
|
||||
- [Getting Started](#getting-started)
|
||||
- [Installation](#installation)
|
||||
+ [Homebrew (for macOS users)](#homebrew-for-macos-users)
|
||||
+ [Go users](#go-users)
|
||||
- [Homebrew (for macOS users)](#homebrew-for-macos-users)
|
||||
- [Scoop (for Windows users)](#scoop-for-windows-users)
|
||||
- [asdf](#asdf)
|
||||
- [Go users](#go-users)
|
||||
- [Docker](#docker)
|
||||
- [Test Installation](#test-installation)
|
||||
- [Quick Start](#quick-start)
|
||||
* [CLI Usage](#cli-usage)
|
||||
- [CLI Usage](#cli-usage)
|
||||
- [`tag query`](#tag-query)
|
||||
* [Configuration](#configuration)
|
||||
* [Templates](#templates)
|
||||
* [Supported Styles](#supported-styles)
|
||||
* [FAQ](#faq)
|
||||
* [TODO](#todo)
|
||||
* [Thanks](#thanks)
|
||||
* [Contributing](#contributing)
|
||||
- [Development](#development)
|
||||
- [Feedback](#feedback)
|
||||
* [CHANGELOG](#changelog)
|
||||
* [Related Projects](#related-projects)
|
||||
* [License](#license)
|
||||
|
||||
|
||||
|
||||
- [Configuration](#configuration)
|
||||
- [`bin`](#bin)
|
||||
- [`style`](#style)
|
||||
- [`template`](#template)
|
||||
- [`info`](#info)
|
||||
- [`options`](#options)
|
||||
- [`options.sort`](#optionssort)
|
||||
- [`options.commits`](#optionscommits)
|
||||
- [`options.commit_groups`](#optionscommit_groups)
|
||||
- [`options.header`](#optionsheader)
|
||||
- [`options.issues`](#optionsissues)
|
||||
- [`options.refs`](#optionsrefs)
|
||||
- [`options.merges`](#optionsmerges)
|
||||
- [`options.reverts`](#optionsreverts)
|
||||
- [`options.notes`](#optionsnotes)
|
||||
- [Templates](#templates)
|
||||
- [Supported Styles](#supported-styles)
|
||||
- [Jira Integration](#jira-integration)
|
||||
- [1. Change the header parse pattern to recognize Jira issue id in the configure file](#1-change-the-header-parse-pattern-to-recognize-jira-issue-id-in-the-configure-file)
|
||||
- [2. Add Jira configuration to the configure file](#2-add-jira-configuration-to-the-configure-file)
|
||||
- [3. Update the template to show Jira data](#3-update-the-template-to-show-jira-data)
|
||||
- [FAQ](#faq)
|
||||
- [TODO](#todo)
|
||||
- [Thanks](#thanks)
|
||||
- [Contributing](#contributing)
|
||||
- [Development](#development)
|
||||
- [Release Process](#release-process)
|
||||
- [Feedback](#feedback)
|
||||
- [CHANGELOG](#changelog)
|
||||
- [Related Projects](#related-projects)
|
||||
- [License](#license)
|
||||
|
||||
## Features
|
||||
|
||||
* :recycle: High portability
|
||||
- It works with single binary. Therefore, any project (environment) can be used.
|
||||
* :beginner: Simple usability
|
||||
- The CLI usage is very simple and has low learning costs.
|
||||
- For example, the simplest command is `$ git-chglog`.
|
||||
* :rocket: High flexibility
|
||||
- Commit message format and ...
|
||||
- CHANGELOG's style (Template) and ...
|
||||
- etc ...
|
||||
|
||||
|
||||
|
||||
- :recycle: High portability
|
||||
- It works with single binary. Therefore, any project (environment) can be used.
|
||||
- :beginner: Simple usability
|
||||
- The CLI usage is very simple and has low learning costs.
|
||||
- For example, the simplest command is `$ git-chglog`.
|
||||
- :rocket: High flexibility
|
||||
- Commit message format and ...
|
||||
- CHANGELOG's style (Template) and ...
|
||||
- etc ...
|
||||
|
||||
## How it works
|
||||
|
||||
`git-chglog` internally uses the `git` command to get data to include in CHANGELOG.
|
||||
The basic steps are as follows.
|
||||
`git-chglog` internally uses the `git` command to get data to include in the
|
||||
CHANGELOG. The basic steps are as follows.
|
||||
|
||||
1. Get all the tags.
|
||||
1. Get the commit contained between `tagA` and `tagB`.
|
||||
1. Execute with all tags corresponding to [tag query](#tag-query) that specified Step 1 to 2.
|
||||
|
||||
|
||||
|
||||
1. Get the commits contained between `tagA` and `tagB`.
|
||||
1. Execute with all tags corresponding to [tag query](#tag-query) that were specified in Step 1 and 2.
|
||||
|
||||
## Getting Started
|
||||
|
||||
We will start with installation and introduce the steps up to the automatic generation of the configuration file and template.
|
||||
|
||||
We will start with installation and introduce the steps up to the automatic generation
|
||||
of the configuration file and template.
|
||||
|
||||
### Installation
|
||||
|
||||
Please install `git-chglog` in a way that matches your environment.
|
||||
|
||||
#### Homebrew (for macOS users)
|
||||
#### [Homebrew](https://brew.sh) (for macOS users)
|
||||
|
||||
```bash
|
||||
$ brew tap git-chglog/git-chglog
|
||||
$ brew install git-chglog
|
||||
brew tap git-chglog/git-chglog
|
||||
brew install git-chglog
|
||||
```
|
||||
|
||||
#### [Scoop](https://scoop.sh) (for Windows users)
|
||||
|
||||
```bash
|
||||
scoop install git-chglog
|
||||
```
|
||||
|
||||
#### [asdf](https://asdf-vm.com/)
|
||||
|
||||
```bash
|
||||
asdf plugin-add git-chglog https://github.com/GoodwayGroup/asdf-git-chglog.git
|
||||
asdf install git-chglog latest
|
||||
```
|
||||
|
||||
#### Go users
|
||||
|
||||
```bash
|
||||
$ go get -u github.com/git-chglog/git-chglog/cmd/git-chglog
|
||||
go install github.com/git-chglog/git-chglog/cmd/git-chglog@latest
|
||||
```
|
||||
|
||||
### [Docker](https://www.docker.com/)
|
||||
The compiled docker images are maintained on [quay.io](https://quay.io/repository/git-chglog/git-chglog).
|
||||
We maintain the following tags:
|
||||
- `edge`: Image that is build from the current `HEAD` of the main line branch.
|
||||
- `latest`: Image that is built from the [latest released version](https://github.com/git-chglog/git-chglog/releases)
|
||||
- `x.y.y` (versions): Images that are build from the tagged versions within Github.
|
||||
```bash
|
||||
docker pull quay.io/git-chglog/git-chglog:latest
|
||||
docker run -v "$PWD":/workdir quay.io/git-chglog/git-chglog --version
|
||||
```
|
||||
---
|
||||
|
||||
If you are in another platform, you can download binary from [release page](https://github.com/git-chglog/git-chglog/releases) and place it in `$PATH` directory.
|
||||
|
||||
If you are using another platform, you can download a binary from the [releases page]
|
||||
and place it in a directory in your `$PATH`.
|
||||
|
||||
### Test Installation
|
||||
|
||||
You can check with the following command whether the `git-chglog` command was included in a valid `$PATH`.
|
||||
You can check with the following command whether the `git-chglog` command was
|
||||
included in a directory that is in your `$PATH`.
|
||||
|
||||
```bash
|
||||
$ git-chglog --version
|
||||
# output the git-chglog version
|
||||
# outputs the git-chglog version
|
||||
```
|
||||
|
||||
|
||||
### Quick Start
|
||||
|
||||
`git-chglog` requires configuration files and templates to generate CHANGELOG.
|
||||
However, it is a waste of time to create configuration files and templates with scratch.
|
||||
`git-chglog` requires configuration files and templates to generate a CHANGELOG.
|
||||
|
||||
Therefore we recommend using the `--init` option which can create them interactively :+1:
|
||||
However, it is a waste of time to create configuration files and templates from scratch.
|
||||
|
||||
Therefore we recommend using the `--init` option which will create them interactively :+1:
|
||||
|
||||
```bash
|
||||
$ git-chglog --init
|
||||
git-chglog --init
|
||||
```
|
||||
|
||||

|
||||
|
|
@ -121,26 +158,26 @@ $ git-chglog --init
|
|||
|
||||
You are now ready for configuration files and templates!
|
||||
|
||||
Let's immediately generate CHANGELOG of your project.
|
||||
By doing the following simple command, Markdown of CHANGELOG is displayed on stdout.
|
||||
Let's immediately generate a CHANGELOG of your project.
|
||||
By doing the following simple command, Markdown for your CHANGELOG is displayed
|
||||
on stdout.
|
||||
|
||||
```bash
|
||||
$ git-chglog
|
||||
git-chglog
|
||||
```
|
||||
|
||||
Use `-o` (`--output`) option if you want to output to file instead of stdout.
|
||||
Use `-o` (`--output`) option if you want to output to a file instead of stdout.
|
||||
|
||||
```bash
|
||||
$ git-chglog -o CHANGELOG.md
|
||||
git-chglog -o CHANGELOG.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
This is how basic usage is over!
|
||||
In order to make better CHANGELOG, please refer to the following document and customize it.
|
||||
|
||||
|
||||
You now know basic usage of `git-chglog`!
|
||||
|
||||
In order to make a better CHANGELOG, please refer to the following document and
|
||||
customize it.
|
||||
|
||||
## CLI Usage
|
||||
|
||||
|
|
@ -152,20 +189,30 @@ USAGE:
|
|||
|
||||
There are the following specification methods for <tag query>.
|
||||
|
||||
1. <old>..<new> - Commit contained in <new> tags from <old>.
|
||||
1. <old>..<new> - Commit contained in <old> tags from <new>.
|
||||
2. <name>.. - Commit from the <name> to the latest tag.
|
||||
3. ..<name> - Commit from the oldest tag to <name>.
|
||||
4. <name> - Commit contained in <name>.
|
||||
|
||||
OPTIONS:
|
||||
--init generate the git-chglog configuration file in interactive
|
||||
--config value, -c value specifies a different configuration file to pick up (default: ".chglog/config.yml")
|
||||
--output value, -o value output path and filename for the changelogs. If not specified, output to stdout
|
||||
--silent disable stdout output
|
||||
--no-color disable color output [$NO_COLOR]
|
||||
--no-emoji disable emoji output [$NO_EMOJI]
|
||||
--help, -h show help
|
||||
--version, -v print the version
|
||||
--init generate the git-chglog configuration file in interactive (default: false)
|
||||
--path value Filter commits by path(s). Can use multiple times.
|
||||
--config value, -c value specifies a different configuration file to pick up (default: ".chglog/config.yml")
|
||||
--template value, -t value specifies a template file to pick up. If not specified, use the one in config
|
||||
--repository-url value specifies git repo URL. If not specified, use 'repository_url' in config
|
||||
--output value, -o value output path and filename for the changelogs. If not specified, output to stdout
|
||||
--next-tag value treat unreleased commits as specified tags (EXPERIMENTAL)
|
||||
--silent disable stdout output (default: false)
|
||||
--no-color disable color output (default: false) [$NO_COLOR]
|
||||
--no-emoji disable emoji output (default: false) [$NO_EMOJI]
|
||||
--no-case disable case sensitive filters (default: false)
|
||||
--tag-filter-pattern value Regular expression of tag filter. Is specified, only matched tags will be picked
|
||||
--jira-url value Jira URL [$JIRA_URL]
|
||||
--jira-username value Jira username [$JIRA_USERNAME]
|
||||
--jira-token value Jira token [$JIRA_TOKEN]
|
||||
--sort value Specify how to sort tags; currently supports "date" or by "semver" (default: date)
|
||||
--help, -h show help (default: false)
|
||||
--version, -v print the version (default: false)
|
||||
|
||||
EXAMPLE:
|
||||
|
||||
|
|
@ -192,13 +239,17 @@ EXAMPLE:
|
|||
|
||||
$ git-chglog --config custom/dir/config.yml
|
||||
|
||||
The adove is a command that uses a configuration file placed other than ".chglog/config.yml".
|
||||
```
|
||||
The above is a command that uses a configuration file placed other than ".chglog/config.yml".
|
||||
|
||||
$ git-chglog --path path/to/my/component --output CHANGELOG.component.md
|
||||
|
||||
Filter commits by specific paths or files in git and output to a component specific changelog.
|
||||
```
|
||||
|
||||
### `tag query`
|
||||
|
||||
You can specify a commit to include in the generation of CHANGELOG using `<tag query>`.
|
||||
You can specify which commits to include in the generation of CHANGELOG using `<tag query>`.
|
||||
|
||||
The table below shows Query patterns and summaries, and Query examples.
|
||||
|
||||
| Query | Description | Example |
|
||||
|
|
@ -208,12 +259,10 @@ The table below shows Query patterns and summaries, and Query examples.
|
|||
| `..<name>` | Commit from the oldest tag to `<name>`. | `$ git-chglog ..2.0.0` |
|
||||
| `<name>` | Commit contained in `<name>`. | `$ git-chglog 1.0.0` |
|
||||
|
||||
|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
The `git-chglog` configuration is write with the yaml file. Default location is `.chglog/config.yml`.
|
||||
The `git-chglog` configuration is a yaml file. The default location is
|
||||
`.chglog/config.yml`.
|
||||
|
||||
Below is a complete list that you can use with `git-chglog`.
|
||||
|
||||
|
|
@ -226,6 +275,9 @@ info:
|
|||
repository_url: https://github.com/git-chglog/git-chglog
|
||||
|
||||
options:
|
||||
tag_filter_pattern: '^v'
|
||||
sort: "date"
|
||||
|
||||
commits:
|
||||
filters:
|
||||
Type:
|
||||
|
|
@ -235,6 +287,8 @@ options:
|
|||
commit_groups:
|
||||
group_by: Type
|
||||
sort_by: Title
|
||||
title_order:
|
||||
- feat
|
||||
title_maps:
|
||||
feat: Features
|
||||
|
||||
|
|
@ -267,7 +321,6 @@ options:
|
|||
- BREAKING CHANGE
|
||||
```
|
||||
|
||||
|
||||
### `bin`
|
||||
|
||||
Git execution command.
|
||||
|
|
@ -276,57 +329,65 @@ Git execution command.
|
|||
|:---------|:-------|:--------|:------------|
|
||||
| N | String | `"git"` | - |
|
||||
|
||||
|
||||
### `style`
|
||||
|
||||
CHANGELOG style. Automatic linking of issues and notices, initial value setting such as merges etc. are done automatically.
|
||||
|
||||
| Required | Type | Default | Description |
|
||||
|:---------|:-------|:---------|:------------------------------|
|
||||
| N | String | `"none"` | Should be `"github"` `"none"` |
|
||||
CHANGELOG style. Automatic linking of issues and notices, initial value setting
|
||||
such as merges etc. are done automatically.
|
||||
|
||||
| Required | Type | Default | Description |
|
||||
|:---------|:-------|:---------|:-------------------------------------------------------|
|
||||
| N | String | `"none"` | Should be `"github"` `"gitlab"` `"bitbucket"` `"none"` |
|
||||
|
||||
### `template`
|
||||
|
||||
Path for template file. It is specified by a relative path from the setting file. Absolute pass is ok.
|
||||
Path for the template file. It is specified by a relative path from the setting
|
||||
file. Absolute paths are also ok.
|
||||
|
||||
| Required | Type | Default | Description |
|
||||
|:---------|:-------|:---------------------|:------------|
|
||||
| N | String | `"CHANGELOG.tpl.md"` | - |
|
||||
|
||||
|
||||
### `info`
|
||||
|
||||
Metadata for CHANGELOG. Depending on Style, it is sometimes used in processing, so it is recommended to specify it.
|
||||
Metadata for CHANGELOG. Depending on Style, it is sometimes used in processing,
|
||||
so it is recommended to specify it.
|
||||
|
||||
| Key | Required | Type | Default | Description |
|
||||
|:-----------------|:---------|:-------|:--------------|:-----------------------|
|
||||
| `title` | N | String | `"CHANGELOG"` | Title of CHANGELOG. |
|
||||
| `repository_url` | N | String | none | URL of git repository. |
|
||||
|
||||
|
||||
### `options`
|
||||
|
||||
Options used to process commits.
|
||||
|
||||
#### `options.sort`
|
||||
|
||||
Options concerning the acquisition and sort of commits.
|
||||
|
||||
| Required | Type | Default | Description |
|
||||
|:---------|:------------|:----------|:--------------------------------------------------------------------------------------------------------------------|
|
||||
| N | String | `"date"` | Defines how tags are sorted in the generated change log. Values: "date", "semver". |
|
||||
|
||||
#### `options.commits`
|
||||
|
||||
Option concerning acquisition and sort of commit.
|
||||
Options concerning the acquisition and sort of commits.
|
||||
|
||||
| Key | Required | Type | Default | Description |
|
||||
|:----------|:---------|:------------|:----------|:--------------------------------------------------------------------------------------------------------------------|
|
||||
| `filters` | N | Map in List | none | Filter by using `Commit` properties and values. Filtering is not done by specifying an empty value. |
|
||||
| `sort_by` | N | String | `"Scope"` | Property name to use for sorting `Commit`. See [Commit](https://godoc.org/github.com/git-chglog/git-chglog#Commit). |
|
||||
| Key | Required | Type | Default | Description |
|
||||
|:----------|:---------|:------------|:----------|:----------------------------------------------------------------------------------------------------|
|
||||
| `filters` | N | Map in List | none | Filter by using `Commit` properties and values. Filtering is not done by specifying an empty value. |
|
||||
| `sort_by` | N | String | `"Scope"` | Property name to use for sorting `Commit`. See [Commit]. |
|
||||
|
||||
#### `options.commit_groups`
|
||||
|
||||
Option for groups of commits.
|
||||
Options for groups of commits.
|
||||
|
||||
| Key | Required | Type | Default | Description |
|
||||
|:-------------|:---------|:------------|:----------|:-------------------------------------------------------------------------------------------|
|
||||
| `group_by` | N | String | `"Type"` | Property name of `Commit` to be grouped into `CommitGroup`. See [CommitGroup][doc-commit]. |
|
||||
| `sort_by` | N | String | `"Title"` | Property name to use for sorting `CommitGroup`. See [CommitGroup][doc-commit-group]. |
|
||||
| `title_maps` | N | Map in List | none | Map for `CommitGroup` title conversion. |
|
||||
| Key | Required | Type | Default | Description |
|
||||
|:--------------|:---------|:------------|:----------|:-------------------------------------------------------------------------------------------|
|
||||
| `group_by` | N | String | `"Type"` | Property name of `Commit` to be grouped into `CommitGroup`. See [CommitGroup][doc-commit]. |
|
||||
| `sort_by` | N | String | `"Title"` | Property name to use for sorting `CommitGroup`. See [CommitGroup][doc-commit-group]. |
|
||||
| `title_order` | N | List | none | Predefined order of titles to use for sorting `CommitGroup`. Only if `sort_by` is `Custom` |
|
||||
| `title_maps` | N | Map in List | none | Map for `CommitGroup` title conversion. |
|
||||
|
||||
#### `options.header`
|
||||
|
||||
|
|
@ -355,7 +416,7 @@ This option is for parsing references.
|
|||
|
||||
#### `options.merges`
|
||||
|
||||
Option to detect and parse merge commit.
|
||||
Options to detect and parse merge commits.
|
||||
|
||||
| Key | Required | Type | Default | Description |
|
||||
|:---------------|:---------|:-------|:--------|:------------------------------------------|
|
||||
|
|
@ -364,7 +425,7 @@ Option to detect and parse merge commit.
|
|||
|
||||
#### `options.reverts`
|
||||
|
||||
Option to detect and parse revert commit.
|
||||
Options to detect and parse revert commits.
|
||||
|
||||
| Key | Required | Type | Default | Description |
|
||||
|:---------------|:---------|:-------|:--------|:------------------------------------------|
|
||||
|
|
@ -373,22 +434,32 @@ Option to detect and parse revert commit.
|
|||
|
||||
#### `options.notes`
|
||||
|
||||
Option to detect notes contained in commit body.
|
||||
Options to detect notes contained in commit bodies.
|
||||
|
||||
| Key | Required | Type | Default | Description |
|
||||
|:-----------|:---------|:-----|:--------|:-----------------------------------------------------------------------------------------------------|
|
||||
| `keywords` | N | List | none | Keyword list to find `Note`. A semicolon is a separator, like `<keyword>:` (e.g. `BREAKING CHANGE`). |
|
||||
|
||||
|
||||
|
||||
|
||||
## Templates
|
||||
|
||||
The `git-chglog` template uses the `text/template` package. For basic usage please refer to the following.
|
||||
The `git-chglog` template uses the `text/template` package and enhanced templating functions provided by [Sprig]. For basic usage please refer to the following.
|
||||
|
||||
> [text/template](https://golang.org/pkg/text/template/)
|
||||
- [text/template](https://golang.org/pkg/text/template/)
|
||||
- [Sprig]
|
||||
|
||||
If you are not satisfied with the prepared template please try customizing.
|
||||
We have implemented the following custom template functions. These override functions provided by [Sprig].
|
||||
|
||||
| Name | Signature | Description |
|
||||
| :----------- | :-------------------------------------------- | :---------------------------------------------------------------------------- |
|
||||
| `contains` | `func(s, substr string) bool` | Reports whether `substr` is within `s` using `strings.Contains` |
|
||||
| `datetime` | `func(layout string, input time.Time) string` | Generate a formatted Date string based on layout |
|
||||
| `hasPrefix` | `func(s, prefix string) bool` | Tests whether the string `s` begins with `prefix` using `strings.HasPrefix` |
|
||||
| `hasSuffix` | `func(s, suffix string) bool` | Tests whether the string `s` ends with `suffix`. using `strings.HasPrefix` |
|
||||
| `indent` | `func(s string, n int) string` | Indent all lines of `s` by `n` spaces |
|
||||
| `replace` | `func(s, old, new string, n int) string` | Replace `old` with `new` within string `s`, `n` times using `strings.Replace` |
|
||||
| `upperFirst` | `func(s string) string` | Upper case the first character of a string |
|
||||
|
||||
If you are not satisfied with the prepared template please try customizing one.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -397,129 +468,281 @@ The basic templates are as follows.
|
|||
**Example:**
|
||||
|
||||
```markdown
|
||||
{{range .Versions}}
|
||||
<a name="{{.Tag.Name}}"></a>
|
||||
## {{if .Tag.Previous}}[{{.Tag.Name}}]({{$.Info.RepositoryURL}}/compare/{{.Tag.Previous.Name}}...{{.Tag.Name}}){{else}}{{.Tag.Name}}{{end}} ({{datetime "2006-01-02" .Tag.Date}})
|
||||
{{range .CommitGroups}}
|
||||
### {{.Title}}
|
||||
{{range .Commits}}
|
||||
* {{if ne .Scope ""}}**{{.Scope}}:** {{end}}{{.Subject}}{{end}}
|
||||
{{end}}{{if .RevertCommits}}
|
||||
{{ if .Versions -}}
|
||||
<a name="unreleased"></a>
|
||||
## [Unreleased]
|
||||
|
||||
{{ if .Unreleased.CommitGroups -}}
|
||||
{{ range .Unreleased.CommitGroups -}}
|
||||
### {{ .Title }}
|
||||
{{ range .Commits -}}
|
||||
- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
|
||||
{{ range .Versions }}
|
||||
<a name="{{ .Tag.Name }}"></a>
|
||||
## {{ if .Tag.Previous }}[{{ .Tag.Name }}]{{ else }}{{ .Tag.Name }}{{ end }} - {{ datetime "2006-01-02" .Tag.Date }}
|
||||
{{ range .CommitGroups -}}
|
||||
### {{ .Title }}
|
||||
{{ range .Commits -}}
|
||||
- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .RevertCommits -}}
|
||||
### Reverts
|
||||
{{range .RevertCommits}}
|
||||
* {{.Header}}{{end}}
|
||||
{{end}}{{if .MergeCommits}}
|
||||
{{ range .RevertCommits -}}
|
||||
- {{ .Revert.Header }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .MergeCommits -}}
|
||||
### Pull Requests
|
||||
{{range .MergeCommits}}
|
||||
* {{.Header}}{{end}}
|
||||
{{end}}{{range .NoteGroups}}
|
||||
### {{.Title}}
|
||||
{{range .Notes}}
|
||||
{{.Body}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{ range .MergeCommits -}}
|
||||
- {{ .Header }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .NoteGroups -}}
|
||||
{{ range .NoteGroups -}}
|
||||
### {{ .Title }}
|
||||
{{ range .Notes }}
|
||||
{{ .Body }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .Versions }}
|
||||
[Unreleased]: {{ .Info.RepositoryURL }}/compare/{{ $latest := index .Versions 0 }}{{ $latest.Tag.Name }}...HEAD
|
||||
{{ range .Versions -}}
|
||||
{{ if .Tag.Previous -}}
|
||||
[{{ .Tag.Name }}]: {{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
```
|
||||
|
||||
See godoc [RenderData][doc-render-data] for available variables.
|
||||
|
||||
|
||||
See the godoc [RenderData][doc-render-data] documentation for available variables.
|
||||
|
||||
## Supported Styles
|
||||
|
||||
| Name | Status | Features |
|
||||
|:-------------------------------------------|:----------------------|:-------------------------------------------------------|
|
||||
| [GitHub](https://github.com/) | :white_check_mark: | Mentions automatic link. Automatic link to references. |
|
||||
| [GitLab](https://about.gitlab.com/) | :white_medium_square: | - |
|
||||
| [Bitbucket](https://bitbucket.org/product) | :white_medium_square: | - |
|
||||
| Name | Status | Features |
|
||||
|:-------------------------------------------|:-------------------|:-------------------------------------------------------|
|
||||
| [GitHub](https://github.com/) | :white_check_mark: | Mentions automatic link. Automatic link to references. |
|
||||
| [GitLab](https://about.gitlab.com/) | :white_check_mark: | Mentions automatic link. Automatic link to references. |
|
||||
| [Bitbucket](https://bitbucket.org/product) | :white_check_mark: | Mentions automatic link. Automatic link to references. |
|
||||
|
||||
> :memo: Even with styles that are not yet supported, it is possible to make ordinary CHANGELOG.
|
||||
> :memo: Even with styles that are not yet supported, it is possible to make
|
||||
ordinary CHANGELOG.
|
||||
|
||||
## Jira Integration
|
||||
|
||||
Jira is a popular project management tool. When a project uses Jira to track
|
||||
feature development and bug fixes, it may also want to generate change log based
|
||||
information stored in Jira. With embedding a Jira story id in git commit header,
|
||||
the git-chglog tool may automatically fetch data of the story from Jira, those
|
||||
data then can be used to render the template.
|
||||
|
||||
Take the following steps to add Jira integration:
|
||||
|
||||
### 1. Change the header parse pattern to recognize Jira issue id in the configure file
|
||||
|
||||
__Where Jira issue is identical Jira story.__
|
||||
|
||||
The following is a sample pattern:
|
||||
|
||||
```yaml
|
||||
header:
|
||||
pattern: "^(?:(\\w*)|(?:\\[(.*)\\])?)\\:\\s(.*)$"
|
||||
pattern_maps:
|
||||
- Type
|
||||
- JiraIssueID
|
||||
- Subject
|
||||
```
|
||||
|
||||
This sample pattern can match both forms of commit headers:
|
||||
|
||||
- `feat: new feature of something`
|
||||
- `[JIRA-ID]: something`
|
||||
|
||||
### 2. Add Jira configuration to the configure file
|
||||
|
||||
The following is a sample:
|
||||
|
||||
```yaml
|
||||
jira:
|
||||
info:
|
||||
username: u
|
||||
token: p
|
||||
url: https://jira.com
|
||||
issue:
|
||||
type_maps:
|
||||
Task: fix
|
||||
Story: feat
|
||||
description_pattern: "<changelog>(.*)</changelog>"
|
||||
```
|
||||
|
||||
Here you need to define Jira URL, access username and token (password). If you
|
||||
don't want to write your Jira access credential in configure file, you may define
|
||||
them with environment variables: `JIRA_URL`, `JIRA_USERNAME` and `JIRA_TOKEN`.
|
||||
|
||||
You also needs to define a issue type map. In above sample, Jira issue type `Task`
|
||||
will be mapped to `fix` and `Story` will be mapped to `feat`.
|
||||
|
||||
As a Jira story's description could be very long, you might not want to include
|
||||
the entire description into change log. In that case, you may define `description_pattern`
|
||||
like above, so that only content embraced with `<changelog> ... </changelog>`
|
||||
will be included.
|
||||
|
||||
### 3. Update the template to show Jira data
|
||||
|
||||
In the template, if a commit contains a Jira issue id, then you may show Jira
|
||||
data. For example:
|
||||
|
||||
```markdown
|
||||
{{ range .CommitGroups -}}
|
||||
### {{ .Title }}
|
||||
{{ range .Commits -}}
|
||||
- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
|
||||
{{ if .JiraIssue }} {{ .JiraIssue.Description }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
```
|
||||
|
||||
Within a `Commit`, the following Jira data can be used in template:
|
||||
|
||||
- `.JiraIssue.Summary` - Summary of the Jira story
|
||||
- `.JiraIssue.Description` - Description of the Jira story
|
||||
- `.JiraIssue.Type` - Original type of the Jira story, and `.Type` will be mapped type.
|
||||
- `.JiraIssue.Labels` - A list of strings, each is a Jira label.
|
||||
|
||||
## FAQ
|
||||
|
||||
<details>
|
||||
<summary>Why do not you output files by default?</summary>
|
||||
This is not for the purpose of completely automating the generation of CHANGELOG, because it is only for the purpose of assisting generation.
|
||||
This is not for the purpose of completely automating the generation of CHANGELOG
|
||||
files, it is only for assisting generation.
|
||||
|
||||
It is ideal to describe everything included in CHANGELOG in commit. But actually it is very difficult to do it perfectly.
|
||||
It is ideal to describe everything included in CHANGELOG in your commits. But
|
||||
actually it is very difficult to do it perfectly.
|
||||
|
||||
There are times when you need to edit the generated output to write a great CHANGELOG.
|
||||
|
||||
There are times when you need your hands to write a great CHANGELOG.
|
||||
By displaying it on the standard output, it makes it easy to change the contents.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Can I commit CHANGELOG changes before creating tags?</summary>
|
||||
|
||||
Yes, it can be solved by using the `--next-tag` flag.
|
||||
|
||||
For example, let's say you want to upgrade your project to `2.0.0`.
|
||||
You can create CHANGELOG containing `2.0.0` as follows.
|
||||
|
||||
```bash
|
||||
git-chglog --next-tag 2.0.0 -o CHANGELOG.md
|
||||
git commit -am "release 2.0.0"
|
||||
git tag 2.0.0
|
||||
```
|
||||
|
||||
The point to notice is that before actually creating a tag with `git`, it is
|
||||
conveying the next version with `--next-tag` :+1:
|
||||
|
||||
This is a step that is necessary for project operation in many cases.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Can I generate a CHANGELOG based on certain tags?</summary>
|
||||
|
||||
Yes, it can be solved by use the `--tag-filter-pattern` flag.
|
||||
|
||||
For example, the following command will only include tags starting with "v":
|
||||
|
||||
```bash
|
||||
git-chglog --tag-filter-pattern '^v'
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## TODO
|
||||
|
||||
* [ ] More styles (GitLab, Bitbucket, and others ...)
|
||||
* [ ] Snippetization of configuration files (improvement of reusability)
|
||||
* [ ] Windows Support
|
||||
* [ ] More test test test ... (and example)
|
||||
|
||||
|
||||
|
||||
- [x] Windows Support
|
||||
- [x] More styles (GitHub, GitLab, Bitbucket :tada:)
|
||||
- [ ] Snippetization of configuration files (improvement of reusability)
|
||||
- [ ] More test test test ... (and example)
|
||||
|
||||
## Thanks
|
||||
|
||||
`git-chglog` is inspired by [conventional-changelog](https://github.com/conventional-changelog/conventional-changelog). Thank you!
|
||||
|
||||
|
||||
|
||||
`git-chglog` is inspired by [conventional-changelog]. Thank you!
|
||||
|
||||
## Contributing
|
||||
|
||||
We are always welcoming your contribution :clap:
|
||||
We always welcome your contributions :clap:
|
||||
|
||||
## Development
|
||||
|
||||
### Development
|
||||
|
||||
1. Use Golang version `>= 1.24`
|
||||
1. Fork (https://github.com/git-chglog/git-chglog) :tada:
|
||||
1. Create a feature branch :coffee:
|
||||
1. Run test suite with the `$ make test` command and confirm that it passes :zap:
|
||||
1. Run linters with the `$ make lint` command and confirm it passes :broom:
|
||||
- The project uses [golangci-lint]
|
||||
1. Commit your changes :memo:
|
||||
1. Rebase your local changes against the `master` branch :bulb:
|
||||
1. Create new Pull Request :love_letter:
|
||||
|
||||
Bugs, feature requests and comments are more than welcome in the [issues](https://github.com/git-chglog/git-chglog/issues).
|
||||
Bugs, feature requests and comments are more than welcome in the [issues].
|
||||
|
||||
## Release Process
|
||||
|
||||
### Feedback
|
||||
There is a `release` target within the Makefile that wraps up the steps to
|
||||
release a new version.
|
||||
|
||||
I would like to make `git-chglog` a better tool.
|
||||
> NOTE: Pass the `VERSION` variable when running the command to properly set
|
||||
> the tag version for the release.
|
||||
|
||||
```bash
|
||||
$ VERSION=vX.Y.Z make release
|
||||
# EXAMPLE:
|
||||
$ VERSION=v0.11.3 make release
|
||||
```
|
||||
|
||||
Once the `tag` has been pushed, the `goreleaser` github action will take care
|
||||
of the rest.
|
||||
|
||||
## Feedback
|
||||
|
||||
I would like to make `git-chglog` a better tool.
|
||||
The goal is to be able to use in various projects.
|
||||
|
||||
Therefore, your feedback is very useful.
|
||||
Therefore, your feedback is very useful.
|
||||
I am very happy to tell you your opinions on Issues and PR :heart:
|
||||
|
||||
|
||||
|
||||
|
||||
## CHANGELOG
|
||||
|
||||
See [CHANGELOG.md](./CHANGELOG.md)
|
||||
|
||||
|
||||
|
||||
|
||||
## Related Projects
|
||||
|
||||
* [git-chglog/artwork](https://github.com/git-chglog/artwork) - Assets for `git-chglog`.
|
||||
|
||||
|
||||
|
||||
- [git-chglog/artwork] - Assets for `git-chglog`.
|
||||
|
||||
## License
|
||||
|
||||
[MIT © tsuyoshiwada](./LICENSE)
|
||||
|
||||
|
||||
|
||||
|
||||
[releases page]: https://github.com/git-chglog/git-chglog/releases
|
||||
[Commit]: https://godoc.org/github.com/git-chglog/git-chglog#Commit
|
||||
[doc-commit]: https://godoc.org/github.com/git-chglog/git-chglog#Commit
|
||||
[doc-commit-group]: https://godoc.org/github.com/git-chglog/git-chglog#Commit
|
||||
[doc-commit-group]: https://godoc.org/github.com/git-chglog/git-chglog#CommitGroup
|
||||
[doc-ref]: https://godoc.org/github.com/git-chglog/git-chglog#Ref
|
||||
[doc-render-data]: https://godoc.org/github.com/git-chglog/git-chglog#RenderData
|
||||
[conventional-changelog]: https://github.com/conventional-changelog/conventional-changelog
|
||||
[golangci-lint]: https://golangci-lint.run/usage/install/#local-installation
|
||||
[issues]: https://github.com/git-chglog/git-chglog/issues
|
||||
[git-chglog/artwork]: https://github.com/git-chglog/artwork
|
||||
[Sprig]: http://masterminds.github.io/sprig
|
||||
|
|
|
|||
223
chglog.go
223
chglog.go
|
|
@ -2,33 +2,48 @@
|
|||
package chglog
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
gitcmd "github.com/tsuyoshiwada/go-gitcmd"
|
||||
"github.com/Masterminds/sprig/v3"
|
||||
"github.com/tsuyoshiwada/go-gitcmd"
|
||||
)
|
||||
|
||||
// Options is an option used to process commits
|
||||
type Options struct {
|
||||
Processor Processor
|
||||
CommitFilters map[string][]string // Filter by using `Commit` properties and values. Filtering is not done by specifying an empty value
|
||||
CommitSortBy string // Property name to use for sorting `Commit` (e.g. `Scope`)
|
||||
CommitGroupBy string // Property name of `Commit` to be grouped into `CommitGroup` (e.g. `Type`)
|
||||
CommitGroupSortBy string // Property name to use for sorting `CommitGroup` (e.g. `Title`)
|
||||
CommitGroupTitleMaps map[string]string // Map for `CommitGroup` title conversion
|
||||
HeaderPattern string // A regular expression to use for parsing the commit header
|
||||
HeaderPatternMaps []string // A rule for mapping the result of `HeaderPattern` to the property of `Commit`
|
||||
IssuePrefix []string // Prefix used for issues (e.g. `#`, `gh-`)
|
||||
RefActions []string // Word list of `Ref.Action`
|
||||
MergePattern string // A regular expression to use for parsing the merge commit
|
||||
MergePatternMaps []string // Similar to `HeaderPatternMaps`
|
||||
RevertPattern string // A regular expression to use for parsing the revert commit
|
||||
RevertPatternMaps []string // Similar to `HeaderPatternMaps`
|
||||
NoteKeywords []string // Keyword list to find `Note`. A semicolon is a separator, like `<keyword>:` (e.g. `BREAKING CHANGE`)
|
||||
Processor Processor
|
||||
NextTag string // Treat unreleased commits as specified tags (EXPERIMENTAL)
|
||||
TagFilterPattern string // Filter tag by regexp
|
||||
Sort string // Specify how to sort tags; currently supports "date" (default) or by "semver".
|
||||
NoCaseSensitive bool // Filter commits in a case insensitive way
|
||||
CommitFilters map[string][]string // Filter by using `Commit` properties and values. Filtering is not done by specifying an empty value
|
||||
CommitSortBy string // Property name to use for sorting `Commit` (e.g. `Scope`)
|
||||
CommitGroupBy string // Property name of `Commit` to be grouped into `CommitGroup` (e.g. `Type`)
|
||||
CommitGroupSortBy string // Property name to use for sorting `CommitGroup` (e.g. `Title`)
|
||||
CommitGroupTitleOrder []string // Predefined sorted list of titles to use for sorting `CommitGroup`. Only if `CommitGroupSortBy` is `Custom`
|
||||
CommitGroupTitleMaps map[string]string // Map for `CommitGroup` title conversion
|
||||
HeaderPattern string // A regular expression to use for parsing the commit header
|
||||
HeaderPatternMaps []string // A rule for mapping the result of `HeaderPattern` to the property of `Commit`
|
||||
IssuePrefix []string // Prefix used for issues (e.g. `#`, `gh-`)
|
||||
RefActions []string // Word list of `Ref.Action`
|
||||
MergePattern string // A regular expression to use for parsing the merge commit
|
||||
MergePatternMaps []string // Similar to `HeaderPatternMaps`
|
||||
RevertPattern string // A regular expression to use for parsing the revert commit
|
||||
RevertPatternMaps []string // Similar to `HeaderPatternMaps`
|
||||
NoteKeywords []string // Keyword list to find `Note`. A semicolon is a separator, like `<keyword>:` (e.g. `BREAKING CHANGE`)
|
||||
JiraUsername string
|
||||
JiraToken string
|
||||
JiraURL string
|
||||
JiraTypeMaps map[string]string
|
||||
JiraIssueDescriptionPattern string
|
||||
Paths []string // Path filter
|
||||
}
|
||||
|
||||
// Info is metadata related to CHANGELOG
|
||||
|
|
@ -39,8 +54,9 @@ type Info struct {
|
|||
|
||||
// RenderData is the data passed to the template
|
||||
type RenderData struct {
|
||||
Info *Info
|
||||
Versions []*Version
|
||||
Info *Info
|
||||
Unreleased *Unreleased
|
||||
Versions []*Version
|
||||
}
|
||||
|
||||
// Config for generating CHANGELOG
|
||||
|
|
@ -90,11 +106,13 @@ type Generator struct {
|
|||
}
|
||||
|
||||
// NewGenerator receives `Config` and create an new `Generator`
|
||||
func NewGenerator(config *Config) *Generator {
|
||||
func NewGenerator(logger *Logger, config *Config) *Generator {
|
||||
client := gitcmd.New(&gitcmd.Config{
|
||||
Bin: config.Bin,
|
||||
})
|
||||
|
||||
jiraClient := NewJiraClient(config)
|
||||
|
||||
if config.Options.Processor != nil {
|
||||
config.Options.Processor.Bootstrap(config)
|
||||
}
|
||||
|
|
@ -104,9 +122,9 @@ func NewGenerator(config *Config) *Generator {
|
|||
return &Generator{
|
||||
client: client,
|
||||
config: config,
|
||||
tagReader: newTagReader(client),
|
||||
tagReader: newTagReader(client, config.Options.TagFilterPattern, config.Options.Sort),
|
||||
tagSelector: newTagSelector(),
|
||||
commitParser: newCommitParser(client, config),
|
||||
commitParser: newCommitParser(logger, client, jiraClient, config),
|
||||
commitExtractor: newCommitExtractor(config.Options),
|
||||
}
|
||||
}
|
||||
|
|
@ -114,43 +132,69 @@ func NewGenerator(config *Config) *Generator {
|
|||
// Generate gets the commit based on the specified tag `query` and writes the result to `io.Writer`
|
||||
//
|
||||
// tag `query` can be specified with the following rule
|
||||
// <old>..<new> - Commit contained in `<new>` tags from `<old>` (e.g. `1.0.0..2.0.0`)
|
||||
// <tagname>.. - Commit from the `<tagname>` to the latest tag (e.g. `1.0.0..`)
|
||||
// ..<tagname> - Commit from the oldest tag to `<tagname>` (e.g. `..1.0.0`)
|
||||
// <tagname> - Commit contained in `<tagname>` (e.g. `1.0.0`)
|
||||
//
|
||||
// <old>..<new> - Commit contained in `<new>` tags from `<old>` (e.g. `1.0.0..2.0.0`)
|
||||
// <tagname>.. - Commit from the `<tagname>` to the latest tag (e.g. `1.0.0..`)
|
||||
// ..<tagname> - Commit from the oldest tag to `<tagname>` (e.g. `..1.0.0`)
|
||||
// <tagname> - Commit contained in `<tagname>` (e.g. `1.0.0`)
|
||||
func (gen *Generator) Generate(w io.Writer, query string) error {
|
||||
back, err := gen.workdir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer back()
|
||||
defer func() {
|
||||
if err = back(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
versions, err := gen.readVersions(query)
|
||||
tags, first, err := gen.getTags(query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gen.render(w, versions)
|
||||
}
|
||||
|
||||
func (gen *Generator) readVersions(query string) ([]*Version, error) {
|
||||
tags, first, err := gen.getTags(query)
|
||||
unreleased, err := gen.readUnreleased(tags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
versions, err := gen.readVersions(tags, first)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(versions) == 0 {
|
||||
return fmt.Errorf("commits corresponding to \"%s\" was not found", query)
|
||||
}
|
||||
|
||||
return gen.render(w, unreleased, versions)
|
||||
}
|
||||
|
||||
func (gen *Generator) readVersions(tags []*Tag, first string) ([]*Version, error) {
|
||||
next := gen.config.Options.NextTag
|
||||
versions := []*Version{}
|
||||
|
||||
for i, tag := range tags {
|
||||
var rev string
|
||||
var (
|
||||
isNext = next == tag.Name
|
||||
rev string
|
||||
)
|
||||
|
||||
if i+1 < len(tags) {
|
||||
rev = tags[i+1].Name + ".." + tag.Name
|
||||
} else {
|
||||
if first != "" {
|
||||
rev = first + ".." + tag.Name
|
||||
if isNext {
|
||||
if tag.Previous != nil {
|
||||
rev = tag.Previous.Name + "..HEAD"
|
||||
} else {
|
||||
rev = tag.Name
|
||||
rev = "HEAD"
|
||||
}
|
||||
} else {
|
||||
if i+1 < len(tags) {
|
||||
rev = tags[i+1].Name + ".." + tag.Name
|
||||
} else {
|
||||
if first != "" {
|
||||
rev = first + ".." + tag.Name
|
||||
} else {
|
||||
rev = tag.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -169,21 +213,82 @@ func (gen *Generator) readVersions(query string) ([]*Version, error) {
|
|||
RevertCommits: revertCommits,
|
||||
NoteGroups: noteGroups,
|
||||
})
|
||||
}
|
||||
|
||||
if len(versions) == 0 {
|
||||
return nil, fmt.Errorf("a commits corresponding to \"%s\" was not found", query)
|
||||
// Instead of `getTags()`, assign the date to the tag
|
||||
if isNext && len(commits) != 0 {
|
||||
tag.Date = commits[0].Author.Date
|
||||
}
|
||||
}
|
||||
|
||||
return versions, nil
|
||||
}
|
||||
|
||||
func (gen *Generator) readUnreleased(tags []*Tag) (*Unreleased, error) {
|
||||
if gen.config.Options.NextTag != "" {
|
||||
return &Unreleased{}, nil
|
||||
}
|
||||
|
||||
rev := "HEAD"
|
||||
|
||||
if len(tags) > 0 {
|
||||
rev = tags[0].Name + "..HEAD"
|
||||
}
|
||||
|
||||
commits, err := gen.commitParser.Parse(rev)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
commitGroups, mergeCommits, revertCommits, noteGroups := gen.commitExtractor.Extract(commits)
|
||||
|
||||
unreleased := &Unreleased{
|
||||
CommitGroups: commitGroups,
|
||||
Commits: commits,
|
||||
MergeCommits: mergeCommits,
|
||||
RevertCommits: revertCommits,
|
||||
NoteGroups: noteGroups,
|
||||
}
|
||||
|
||||
return unreleased, nil
|
||||
}
|
||||
|
||||
func (gen *Generator) getTags(query string) ([]*Tag, string, error) {
|
||||
tags, err := gen.tagReader.ReadAll()
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
next := gen.config.Options.NextTag
|
||||
if next != "" {
|
||||
for _, tag := range tags {
|
||||
if next == tag.Name {
|
||||
return nil, "", fmt.Errorf("\"%s\" tag already exists", next)
|
||||
}
|
||||
}
|
||||
|
||||
var previous *RelateTag
|
||||
if len(tags) > 0 {
|
||||
previous = &RelateTag{
|
||||
Name: tags[0].Name,
|
||||
Subject: tags[0].Subject,
|
||||
Date: tags[0].Date,
|
||||
}
|
||||
}
|
||||
|
||||
// Assign the date with `readVersions()`
|
||||
tags = append([]*Tag{
|
||||
{
|
||||
Name: next,
|
||||
Subject: next,
|
||||
Previous: previous,
|
||||
},
|
||||
}, tags...)
|
||||
}
|
||||
|
||||
if len(tags) == 0 {
|
||||
return nil, "", errors.New("git-tag does not exist")
|
||||
}
|
||||
|
||||
first := ""
|
||||
if query != "" {
|
||||
tags, first, err = gen.tagSelector.Select(tags, query)
|
||||
|
|
@ -211,23 +316,47 @@ func (gen *Generator) workdir() (func() error, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (gen *Generator) render(w io.Writer, versions []*Version) error {
|
||||
func (gen *Generator) render(w io.Writer, unreleased *Unreleased, versions []*Version) error {
|
||||
if _, err := os.Stat(gen.config.Template); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmap := template.FuncMap{
|
||||
// format the input time according to layout
|
||||
"datetime": func(layout string, input time.Time) string {
|
||||
return input.Format(layout)
|
||||
},
|
||||
// upper case the first character of a string
|
||||
"upperFirst": func(s string) string {
|
||||
if len(s) > 0 {
|
||||
return strings.ToUpper(string(s[0])) + s[1:]
|
||||
}
|
||||
return ""
|
||||
},
|
||||
// indent all lines of s n spaces
|
||||
"indent": func(s string, n int) string {
|
||||
if len(s) == 0 {
|
||||
return ""
|
||||
}
|
||||
pad := strings.Repeat(" ", n)
|
||||
return pad + strings.ReplaceAll(s, "\n", "\n"+pad)
|
||||
},
|
||||
// While Sprig provides these functions, they change the standard input
|
||||
// order which leads to a regression. For an example see:
|
||||
// https://github.com/Masterminds/sprig/blob/master/functions.go#L149
|
||||
"contains": strings.Contains,
|
||||
"hasPrefix": strings.HasPrefix,
|
||||
"hasSuffix": strings.HasSuffix,
|
||||
"replace": strings.Replace,
|
||||
}
|
||||
|
||||
fname := filepath.Base(gen.config.Template)
|
||||
|
||||
t := template.Must(template.New(fname).Funcs(fmap).ParseFiles(gen.config.Template))
|
||||
t := template.Must(template.New(fname).Funcs(sprig.TxtFuncMap()).Funcs(fmap).ParseFiles(gen.config.Template))
|
||||
|
||||
return t.Execute(w, &RenderData{
|
||||
Info: gen.config.Info,
|
||||
Versions: versions,
|
||||
Info: gen.config.Info,
|
||||
Unreleased: unreleased,
|
||||
Versions: versions,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
672
chglog_test.go
672
chglog_test.go
|
|
@ -6,16 +6,21 @@ import (
|
|||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
gitcmd "github.com/tsuyoshiwada/go-gitcmd"
|
||||
)
|
||||
|
||||
var (
|
||||
cwd string
|
||||
testRepoRoot = ".tmp"
|
||||
cwd string
|
||||
testRepoRoot = ".tmp"
|
||||
internalTimeFormat = "2006-01-02 15:04:05"
|
||||
)
|
||||
|
||||
type commitFunc = func(date, subject, body string)
|
||||
type tagFunc = func(name string)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
cwd, _ = os.Getwd()
|
||||
cleanup()
|
||||
|
|
@ -24,162 +29,255 @@ func TestMain(m *testing.M) {
|
|||
os.Exit(code)
|
||||
}
|
||||
|
||||
func setup(dir string, setupRepo func(gitcmd.Client)) {
|
||||
func setup(dir string, setupRepo func(commitFunc, tagFunc, gitcmd.Client)) {
|
||||
testDir := filepath.Join(cwd, testRepoRoot, dir)
|
||||
|
||||
os.MkdirAll(testDir, os.ModePerm)
|
||||
os.Chdir(testDir)
|
||||
_ = os.RemoveAll(testDir)
|
||||
_ = os.MkdirAll(testDir, os.ModePerm)
|
||||
_ = os.Chdir(testDir)
|
||||
|
||||
loc, _ := time.LoadLocation("UTC")
|
||||
time.Local = loc
|
||||
|
||||
git := gitcmd.New(nil)
|
||||
git.Exec("init")
|
||||
git.Exec("config", "user.name", "test_user")
|
||||
git.Exec("config", "user.email", "test@example.com")
|
||||
_, _ = git.Exec("init")
|
||||
_, _ = git.Exec("config", "user.name", "test_user")
|
||||
_, _ = git.Exec("config", "user.email", "test@example.com")
|
||||
|
||||
setupRepo(git)
|
||||
var commit = func(date, subject, body string) {
|
||||
msg := subject
|
||||
if body != "" {
|
||||
msg += "\n\n" + body
|
||||
}
|
||||
t, _ := time.Parse(internalTimeFormat, date)
|
||||
d := t.Format("Mon Jan 2 15:04:05 2006 +0000")
|
||||
_, _ = git.Exec("commit", "--allow-empty", "--date", d, "-m", msg)
|
||||
}
|
||||
|
||||
os.Chdir(cwd)
|
||||
var tag = func(name string) {
|
||||
_, _ = git.Exec("tag", name)
|
||||
}
|
||||
|
||||
setupRepo(commit, tag, git)
|
||||
|
||||
_ = os.Chdir(cwd)
|
||||
}
|
||||
|
||||
func cleanup() {
|
||||
os.Chdir(cwd)
|
||||
os.RemoveAll(filepath.Join(cwd, testRepoRoot))
|
||||
_ = os.Chdir(cwd)
|
||||
_ = os.RemoveAll(filepath.Join(cwd, testRepoRoot))
|
||||
}
|
||||
|
||||
func TestGeneratorNotFoundTags(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
testName := "not_found"
|
||||
|
||||
setup(testName, func(commit commitFunc, _ tagFunc, _ gitcmd.Client) {
|
||||
commit("2018-01-01 00:00:00", "feat(*): New feature", "")
|
||||
})
|
||||
|
||||
gen := NewGenerator(NewLogger(os.Stdout, os.Stderr, false, true),
|
||||
&Config{
|
||||
Bin: "git",
|
||||
WorkingDir: filepath.Join(testRepoRoot, testName),
|
||||
Template: filepath.Join(cwd, "testdata", testName+".md"),
|
||||
Info: &Info{
|
||||
RepositoryURL: "https://github.com/git-chglog/git-chglog",
|
||||
},
|
||||
Options: &Options{},
|
||||
})
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
err := gen.Generate(buf, "")
|
||||
expected := strings.TrimSpace(buf.String())
|
||||
|
||||
assert.Error(err)
|
||||
assert.Contains(err.Error(), "git-tag does not exist")
|
||||
assert.Equal("", expected)
|
||||
}
|
||||
|
||||
func TestGeneratorNotFoundCommits(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
testName := "not_found"
|
||||
|
||||
setup(testName, func(git gitcmd.Client) {
|
||||
setup(testName, func(commit commitFunc, tag tagFunc, _ gitcmd.Client) {
|
||||
commit("2018-01-01 00:00:00", "feat(*): New feature", "")
|
||||
tag("1.0.0")
|
||||
})
|
||||
|
||||
gen := NewGenerator(&Config{
|
||||
Bin: "git",
|
||||
WorkingDir: filepath.Join(testRepoRoot, testName),
|
||||
Template: filepath.Join(cwd, "testdata", testName+".md"),
|
||||
Info: &Info{
|
||||
RepositoryURL: "https://github.com/git-chglog/git-chglog",
|
||||
},
|
||||
Options: &Options{
|
||||
CommitFilters: map[string][]string{},
|
||||
CommitSortBy: "Scope",
|
||||
CommitGroupBy: "Type",
|
||||
CommitGroupSortBy: "Title",
|
||||
CommitGroupTitleMaps: map[string]string{},
|
||||
HeaderPattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$",
|
||||
HeaderPatternMaps: []string{
|
||||
"Type",
|
||||
"Scope",
|
||||
"Subject",
|
||||
gen := NewGenerator(NewLogger(os.Stdout, os.Stderr, false, true),
|
||||
&Config{
|
||||
Bin: "git",
|
||||
WorkingDir: filepath.Join(testRepoRoot, testName),
|
||||
Template: filepath.Join(cwd, "testdata", testName+".md"),
|
||||
Info: &Info{
|
||||
RepositoryURL: "https://github.com/git-chglog/git-chglog",
|
||||
},
|
||||
IssuePrefix: []string{
|
||||
"#",
|
||||
"gh-",
|
||||
},
|
||||
RefActions: []string{},
|
||||
MergePattern: "^Merge pull request #(\\d+) from (.*)$",
|
||||
MergePatternMaps: []string{
|
||||
"Ref",
|
||||
"Source",
|
||||
},
|
||||
RevertPattern: "^Revert \"([\\s\\S]*)\"$",
|
||||
RevertPatternMaps: []string{
|
||||
"Header",
|
||||
},
|
||||
NoteKeywords: []string{
|
||||
"BREAKING CHANGE",
|
||||
},
|
||||
},
|
||||
})
|
||||
Options: &Options{},
|
||||
})
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
err := gen.Generate(buf, "foo")
|
||||
expected := strings.TrimSpace(buf.String())
|
||||
|
||||
assert.Error(err)
|
||||
assert.Equal("", expected)
|
||||
}
|
||||
|
||||
func TestGeneratorNotFoundCommitsOne(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
testName := "not_found"
|
||||
|
||||
setup(testName, func(commit commitFunc, tag tagFunc, _ gitcmd.Client) {
|
||||
commit("2018-01-01 00:00:00", "chore(*): First commit", "")
|
||||
tag("1.0.0")
|
||||
})
|
||||
|
||||
gen := NewGenerator(NewLogger(os.Stdout, os.Stderr, false, true),
|
||||
&Config{
|
||||
Bin: "git",
|
||||
WorkingDir: filepath.Join(testRepoRoot, testName),
|
||||
Template: filepath.Join(cwd, "testdata", testName+".md"),
|
||||
Info: &Info{
|
||||
RepositoryURL: "https://github.com/git-chglog/git-chglog",
|
||||
},
|
||||
Options: &Options{
|
||||
CommitFilters: map[string][]string{},
|
||||
CommitSortBy: "Scope",
|
||||
CommitGroupBy: "Type",
|
||||
CommitGroupSortBy: "Title",
|
||||
CommitGroupTitleMaps: map[string]string{},
|
||||
HeaderPattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$",
|
||||
HeaderPatternMaps: []string{
|
||||
"Type",
|
||||
"Scope",
|
||||
"Subject",
|
||||
},
|
||||
IssuePrefix: []string{
|
||||
"#",
|
||||
"gh-",
|
||||
},
|
||||
RefActions: []string{},
|
||||
MergePattern: "^Merge pull request #(\\d+) from (.*)$",
|
||||
MergePatternMaps: []string{
|
||||
"Ref",
|
||||
"Source",
|
||||
},
|
||||
RevertPattern: "^Revert \"([\\s\\S]*)\"$",
|
||||
RevertPatternMaps: []string{
|
||||
"Header",
|
||||
},
|
||||
NoteKeywords: []string{
|
||||
"BREAKING CHANGE",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
err := gen.Generate(buf, "foo")
|
||||
expected := strings.TrimSpace(buf.String())
|
||||
|
||||
assert.Error(err)
|
||||
assert.Contains(err.Error(), "\"foo\" was not found")
|
||||
assert.Equal("", buf.String())
|
||||
assert.Equal("", expected)
|
||||
}
|
||||
|
||||
func TestGeneratorWithTypeScopeSubject(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
testName := "type_scope_subject"
|
||||
|
||||
setup(testName, func(git gitcmd.Client) {
|
||||
git.Exec("commit", "--allow-empty", "--date", "Mon Jan 1 00:00:00 2018 +0900", "-m", "chore(*): First commit")
|
||||
git.Exec("commit", "--allow-empty", "--date", "Mon Jan 1 00:01:00 2018 +0900", "-m", "feat(core): Add foo bar")
|
||||
git.Exec("commit", "--allow-empty", "--date", "Mon Jan 1 00:02:00 2018 +0900", "-m", "docs(readme): Update usage #123")
|
||||
setup(testName, func(commit commitFunc, tag tagFunc, _ gitcmd.Client) {
|
||||
commit("2018-01-01 00:00:00", "chore(*): First commit", "")
|
||||
commit("2018-01-01 00:01:00", "feat(core): Add foo bar", "")
|
||||
commit("2018-01-01 00:02:00", "docs(readme): Update usage #123", "")
|
||||
tag("1.0.0")
|
||||
|
||||
git.Exec("tag", "1.0.0")
|
||||
git.Exec("commit", "--allow-empty", "--date", "Tue Jan 2 00:00:00 2018 +0900", "-m", "feat(parser): New some super options #333")
|
||||
git.Exec("commit", "--allow-empty", "--date", "Tue Jan 2 00:01:00 2018 +0900", "-m", "Merge pull request #999 from tsuyoshiwada/patch-1")
|
||||
git.Exec("commit", "--allow-empty", "--date", "Tue Jan 2 00:02:00 2018 +0900", "-m", "Merge pull request #1000 from tsuyoshiwada/patch-1")
|
||||
git.Exec("commit", "--allow-empty", "--date", "Tue Jan 2 00:03:00 2018 +0900", "-m", "Revert \"feat(core): Add foo bar @mention and issue #987\"")
|
||||
commit("2018-01-02 00:00:00", "feat(parser): New some super options #333", "")
|
||||
commit("2018-01-02 00:01:00", "Merge pull request #999 from tsuyoshiwada/patch-1", "")
|
||||
commit("2018-01-02 00:02:00", "Merge pull request #1000 from tsuyoshiwada/patch-1", "")
|
||||
commit("2018-01-02 00:03:00", "Revert \"feat(core): Add foo bar @mention and issue #987\"", "")
|
||||
tag("1.1.0")
|
||||
|
||||
git.Exec("tag", "1.1.0")
|
||||
git.Exec("commit", "--allow-empty", "--date", "Wed Jan 3 00:00:00 2018 +0900", "-m", "feat(context): Online breaking change\n\nBREAKING CHANGE: Online breaking change message.")
|
||||
git.Exec("commit", "--allow-empty", "--date", "Wed Jan 3 00:01:00 2018 +0900", "-m", "feat(router): Muliple breaking change\n\nThis is body,\n\nBREAKING CHANGE:\nMultiple\nbreaking\nchange message.")
|
||||
commit("2018-01-03 00:00:00", "feat(context): Online breaking change", "BREAKING CHANGE: Online breaking change message.")
|
||||
commit("2018-01-03 00:01:00", "feat(router): Multiple breaking change", `This is body,
|
||||
|
||||
git.Exec("tag", "2.0.0-beta.0")
|
||||
git.Exec("commit", "--allow-empty", "--date", "Thu Jan 4 00:00:00 2018 +0900", "-m", "refactor(context): gofmt")
|
||||
git.Exec("commit", "--allow-empty", "--date", "Thu Jan 4 00:01:00 2018 +0900", "-m", "fix(core): Fix commit\n\nThis is body message.")
|
||||
BREAKING CHANGE:
|
||||
Multiple
|
||||
breaking
|
||||
change message.`)
|
||||
tag("2.0.0-beta.0")
|
||||
|
||||
commit("2018-01-04 00:00:00", "refactor(context): gofmt", "")
|
||||
commit("2018-01-04 00:01:00", "fix(core): Fix commit\n\nThis is body message.", "")
|
||||
})
|
||||
|
||||
gen := NewGenerator(&Config{
|
||||
Bin: "git",
|
||||
WorkingDir: filepath.Join(testRepoRoot, testName),
|
||||
Template: filepath.Join(cwd, "testdata", testName+".md"),
|
||||
Info: &Info{
|
||||
Title: "CHANGELOG Example",
|
||||
RepositoryURL: "https://github.com/git-chglog/git-chglog",
|
||||
},
|
||||
Options: &Options{
|
||||
CommitFilters: map[string][]string{
|
||||
"Type": []string{
|
||||
"feat",
|
||||
"fix",
|
||||
gen := NewGenerator(NewLogger(os.Stdout, os.Stderr, false, true),
|
||||
&Config{
|
||||
Bin: "git",
|
||||
WorkingDir: filepath.Join(testRepoRoot, testName),
|
||||
Template: filepath.Join(cwd, "testdata", testName+".md"),
|
||||
Info: &Info{
|
||||
Title: "CHANGELOG Example",
|
||||
RepositoryURL: "https://github.com/git-chglog/git-chglog",
|
||||
},
|
||||
Options: &Options{
|
||||
Sort: "date",
|
||||
CommitFilters: map[string][]string{
|
||||
"Type": {
|
||||
"feat",
|
||||
"fix",
|
||||
},
|
||||
},
|
||||
CommitSortBy: "Scope",
|
||||
CommitGroupBy: "Type",
|
||||
CommitGroupSortBy: "Title",
|
||||
CommitGroupTitleMaps: map[string]string{
|
||||
"feat": "Features",
|
||||
"fix": "Bug Fixes",
|
||||
},
|
||||
HeaderPattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$",
|
||||
HeaderPatternMaps: []string{
|
||||
"Type",
|
||||
"Scope",
|
||||
"Subject",
|
||||
},
|
||||
IssuePrefix: []string{
|
||||
"#",
|
||||
"gh-",
|
||||
},
|
||||
RefActions: []string{},
|
||||
MergePattern: "^Merge pull request #(\\d+) from (.*)$",
|
||||
MergePatternMaps: []string{
|
||||
"Ref",
|
||||
"Source",
|
||||
},
|
||||
RevertPattern: "^Revert \"([\\s\\S]*)\"$",
|
||||
RevertPatternMaps: []string{
|
||||
"Header",
|
||||
},
|
||||
NoteKeywords: []string{
|
||||
"BREAKING CHANGE",
|
||||
},
|
||||
},
|
||||
CommitSortBy: "Scope",
|
||||
CommitGroupBy: "Type",
|
||||
CommitGroupSortBy: "Title",
|
||||
CommitGroupTitleMaps: map[string]string{
|
||||
"feat": "Features",
|
||||
"fix": "Bug Fixes",
|
||||
},
|
||||
HeaderPattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$",
|
||||
HeaderPatternMaps: []string{
|
||||
"Type",
|
||||
"Scope",
|
||||
"Subject",
|
||||
},
|
||||
IssuePrefix: []string{
|
||||
"#",
|
||||
"gh-",
|
||||
},
|
||||
RefActions: []string{},
|
||||
MergePattern: "^Merge pull request #(\\d+) from (.*)$",
|
||||
MergePatternMaps: []string{
|
||||
"Ref",
|
||||
"Source",
|
||||
},
|
||||
RevertPattern: "^Revert \"([\\s\\S]*)\"$",
|
||||
RevertPatternMaps: []string{
|
||||
"Header",
|
||||
},
|
||||
NoteKeywords: []string{
|
||||
"BREAKING CHANGE",
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
err := gen.Generate(buf, "")
|
||||
expected := strings.TrimSpace(buf.String())
|
||||
|
||||
assert.Nil(err)
|
||||
assert.Equal(`<a name="2.0.0-beta.0"></a>
|
||||
## 2.0.0-beta.0 (2018-01-03)
|
||||
assert.Equal(`<a name="unreleased"></a>
|
||||
## [Unreleased]
|
||||
|
||||
### Bug Fixes
|
||||
- **core:** Fix commit
|
||||
|
||||
|
||||
<a name="2.0.0-beta.0"></a>
|
||||
## [2.0.0-beta.0] - 2018-01-03
|
||||
### Features
|
||||
|
||||
* **context:** Online breaking change
|
||||
* **router:** Muliple breaking change
|
||||
- **context:** Online breaking change
|
||||
- **router:** Multiple breaking change
|
||||
|
||||
### BREAKING CHANGE
|
||||
|
||||
|
|
@ -190,28 +288,344 @@ change message.
|
|||
Online breaking change message.
|
||||
|
||||
|
||||
|
||||
<a name="1.1.0"></a>
|
||||
## 1.1.0 (2018-01-02)
|
||||
|
||||
## [1.1.0] - 2018-01-02
|
||||
### Features
|
||||
|
||||
* **parser:** New some super options #333
|
||||
- **parser:** New some super options #333
|
||||
|
||||
### Reverts
|
||||
|
||||
* feat(core): Add foo bar @mention and issue #987
|
||||
- feat(core): Add foo bar @mention and issue #987
|
||||
|
||||
### Pull Requests
|
||||
|
||||
* Merge pull request #1000 from tsuyoshiwada/patch-1
|
||||
* Merge pull request #999 from tsuyoshiwada/patch-1
|
||||
- Merge pull request #1000 from tsuyoshiwada/patch-1
|
||||
- Merge pull request #999 from tsuyoshiwada/patch-1
|
||||
|
||||
|
||||
<a name="1.0.0"></a>
|
||||
## 1.0.0 (2018-01-01)
|
||||
|
||||
## 1.0.0 - 2018-01-01
|
||||
### Features
|
||||
- **core:** Add foo bar
|
||||
|
||||
|
||||
[Unreleased]: https://github.com/git-chglog/git-chglog/compare/2.0.0-beta.0...HEAD
|
||||
[2.0.0-beta.0]: https://github.com/git-chglog/git-chglog/compare/1.1.0...2.0.0-beta.0
|
||||
[1.1.0]: https://github.com/git-chglog/git-chglog/compare/1.0.0...1.1.0`, expected)
|
||||
}
|
||||
|
||||
func TestGeneratorWithNextTag(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
testName := "type_scope_subject"
|
||||
|
||||
setup(testName, func(commit commitFunc, tag tagFunc, _ gitcmd.Client) {
|
||||
commit("2018-01-01 00:00:00", "feat(core): version 1.0.0", "")
|
||||
tag("1.0.0")
|
||||
|
||||
commit("2018-02-01 00:00:00", "feat(core): version 2.0.0", "")
|
||||
tag("2.0.0")
|
||||
|
||||
commit("2018-03-01 00:00:00", "feat(core): version 3.0.0", "")
|
||||
})
|
||||
|
||||
gen := NewGenerator(NewLogger(os.Stdout, os.Stderr, false, true),
|
||||
&Config{
|
||||
Bin: "git",
|
||||
WorkingDir: filepath.Join(testRepoRoot, testName),
|
||||
Template: filepath.Join(cwd, "testdata", testName+".md"),
|
||||
Info: &Info{
|
||||
Title: "CHANGELOG Example",
|
||||
RepositoryURL: "https://github.com/git-chglog/git-chglog",
|
||||
},
|
||||
Options: &Options{
|
||||
Sort: "date",
|
||||
NextTag: "3.0.0",
|
||||
CommitFilters: map[string][]string{
|
||||
"Type": {
|
||||
"feat",
|
||||
},
|
||||
},
|
||||
CommitSortBy: "Scope",
|
||||
CommitGroupBy: "Type",
|
||||
CommitGroupSortBy: "Title",
|
||||
CommitGroupTitleMaps: map[string]string{
|
||||
"feat": "Features",
|
||||
},
|
||||
HeaderPattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$",
|
||||
HeaderPatternMaps: []string{
|
||||
"Type",
|
||||
"Scope",
|
||||
"Subject",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
err := gen.Generate(buf, "")
|
||||
expected := strings.TrimSpace(buf.String())
|
||||
|
||||
assert.Nil(err)
|
||||
assert.Equal(`<a name="unreleased"></a>
|
||||
## [Unreleased]
|
||||
|
||||
|
||||
<a name="3.0.0"></a>
|
||||
## [3.0.0] - 2018-03-01
|
||||
### Features
|
||||
- **core:** version 3.0.0
|
||||
|
||||
|
||||
<a name="2.0.0"></a>
|
||||
## [2.0.0] - 2018-02-01
|
||||
### Features
|
||||
- **core:** version 2.0.0
|
||||
|
||||
|
||||
<a name="1.0.0"></a>
|
||||
## 1.0.0 - 2018-01-01
|
||||
### Features
|
||||
- **core:** version 1.0.0
|
||||
|
||||
|
||||
[Unreleased]: https://github.com/git-chglog/git-chglog/compare/3.0.0...HEAD
|
||||
[3.0.0]: https://github.com/git-chglog/git-chglog/compare/2.0.0...3.0.0
|
||||
[2.0.0]: https://github.com/git-chglog/git-chglog/compare/1.0.0...2.0.0`, expected)
|
||||
|
||||
buf = &bytes.Buffer{}
|
||||
err = gen.Generate(buf, "3.0.0")
|
||||
expected = strings.TrimSpace(buf.String())
|
||||
|
||||
assert.Nil(err)
|
||||
assert.Equal(`<a name="unreleased"></a>
|
||||
## [Unreleased]
|
||||
|
||||
|
||||
<a name="3.0.0"></a>
|
||||
## [3.0.0] - 2018-03-01
|
||||
### Features
|
||||
- **core:** version 3.0.0
|
||||
|
||||
|
||||
[Unreleased]: https://github.com/git-chglog/git-chglog/compare/3.0.0...HEAD
|
||||
[3.0.0]: https://github.com/git-chglog/git-chglog/compare/2.0.0...3.0.0`, expected)
|
||||
}
|
||||
|
||||
func TestGeneratorWithTagFiler(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
testName := "type_scope_subject"
|
||||
|
||||
setup(testName, func(commit commitFunc, tag tagFunc, _ gitcmd.Client) {
|
||||
commit("2018-01-01 00:00:00", "feat(core): version dev-1.0.0", "")
|
||||
tag("dev-1.0.0")
|
||||
|
||||
commit("2018-02-01 00:00:00", "feat(core): version v1.0.0", "")
|
||||
tag("v1.0.0")
|
||||
})
|
||||
|
||||
gen := NewGenerator(NewLogger(os.Stdout, os.Stderr, false, true),
|
||||
&Config{
|
||||
Bin: "git",
|
||||
WorkingDir: filepath.Join(testRepoRoot, testName),
|
||||
Template: filepath.Join(cwd, "testdata", testName+".md"),
|
||||
Info: &Info{
|
||||
Title: "CHANGELOG Example",
|
||||
RepositoryURL: "https://github.com/git-chglog/git-chglog",
|
||||
},
|
||||
Options: &Options{
|
||||
TagFilterPattern: "^v",
|
||||
CommitFilters: map[string][]string{
|
||||
"Type": {
|
||||
"feat",
|
||||
},
|
||||
},
|
||||
CommitSortBy: "Scope",
|
||||
CommitGroupBy: "Type",
|
||||
CommitGroupSortBy: "Title",
|
||||
CommitGroupTitleMaps: map[string]string{
|
||||
"feat": "Features",
|
||||
},
|
||||
HeaderPattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$",
|
||||
HeaderPatternMaps: []string{
|
||||
"Type",
|
||||
"Scope",
|
||||
"Subject",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
err := gen.Generate(buf, "")
|
||||
expected := strings.TrimSpace(buf.String())
|
||||
|
||||
assert.Nil(err)
|
||||
assert.Equal(`<a name="unreleased"></a>
|
||||
## [Unreleased]
|
||||
|
||||
|
||||
<a name="v1.0.0"></a>
|
||||
## v1.0.0 - 2018-02-01
|
||||
### Features
|
||||
- **core:** version v1.0.0
|
||||
- **core:** version dev-1.0.0
|
||||
|
||||
|
||||
[Unreleased]: https://github.com/git-chglog/git-chglog/compare/v1.0.0...HEAD`, expected)
|
||||
|
||||
}
|
||||
|
||||
func TestGeneratorWithTrimmedBody(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
testName := "trimmed_body"
|
||||
|
||||
setup(testName, func(commit commitFunc, tag tagFunc, _ gitcmd.Client) {
|
||||
commit("2018-01-01 00:00:00", "feat: single line commit", "")
|
||||
commit("2018-01-01 00:01:00", "feat: multi-line commit", `
|
||||
More details about the change and why it went in.
|
||||
|
||||
BREAKING CHANGE:
|
||||
|
||||
When using .TrimmedBody Notes are not included and can only appear in the Notes section.
|
||||
|
||||
Signed-off-by: First Last <first.last@mail.com>
|
||||
|
||||
Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>`)
|
||||
|
||||
commit("2018-01-01 00:00:00", "feat: another single line commit", "")
|
||||
tag("1.0.0")
|
||||
})
|
||||
|
||||
gen := NewGenerator(NewLogger(os.Stdout, os.Stderr, false, true),
|
||||
&Config{
|
||||
Bin: "git",
|
||||
WorkingDir: filepath.Join(testRepoRoot, testName),
|
||||
Template: filepath.Join(cwd, "testdata", testName+".md"),
|
||||
Info: &Info{
|
||||
Title: "CHANGELOG Example",
|
||||
RepositoryURL: "https://github.com/git-chglog/git-chglog",
|
||||
},
|
||||
Options: &Options{
|
||||
CommitFilters: map[string][]string{
|
||||
"Type": {
|
||||
"feat",
|
||||
},
|
||||
},
|
||||
CommitSortBy: "Scope",
|
||||
CommitGroupBy: "Type",
|
||||
CommitGroupSortBy: "Title",
|
||||
CommitGroupTitleMaps: map[string]string{
|
||||
"feat": "Features",
|
||||
},
|
||||
HeaderPattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$",
|
||||
HeaderPatternMaps: []string{
|
||||
"Type",
|
||||
"Scope",
|
||||
"Subject",
|
||||
},
|
||||
NoteKeywords: []string{
|
||||
"BREAKING CHANGE",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
err := gen.Generate(buf, "")
|
||||
expected := strings.TrimSpace(buf.String())
|
||||
|
||||
assert.Nil(err)
|
||||
assert.Equal(`<a name="unreleased"></a>
|
||||
## [Unreleased]
|
||||
|
||||
|
||||
<a name="1.0.0"></a>
|
||||
## 1.0.0 - 2018-01-01
|
||||
### Features
|
||||
- another single line commit
|
||||
- multi-line commit
|
||||
More details about the change and why it went in.
|
||||
- single line commit
|
||||
|
||||
### BREAKING CHANGE
|
||||
|
||||
When using .TrimmedBody Notes are not included and can only appear in the Notes section.
|
||||
|
||||
|
||||
[Unreleased]: https://github.com/git-chglog/git-chglog/compare/1.0.0...HEAD`, expected)
|
||||
}
|
||||
|
||||
func TestGeneratorWithSprig(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
testName := "with_sprig"
|
||||
|
||||
setup(testName, func(commit commitFunc, tag tagFunc, _ gitcmd.Client) {
|
||||
commit("2018-01-01 00:00:00", "feat(core): version 1.0.0", "")
|
||||
tag("1.0.0")
|
||||
|
||||
commit("2018-02-01 00:00:00", "feat(core): version 2.0.0", "")
|
||||
tag("2.0.0")
|
||||
|
||||
commit("2018-03-01 00:00:00", "feat(core): version 3.0.0", "")
|
||||
})
|
||||
|
||||
gen := NewGenerator(NewLogger(os.Stdout, os.Stderr, false, true),
|
||||
&Config{
|
||||
Bin: "git",
|
||||
WorkingDir: filepath.Join(testRepoRoot, testName),
|
||||
Template: filepath.Join(cwd, "testdata", testName+".md"),
|
||||
Info: &Info{
|
||||
Title: "CHANGELOG Example",
|
||||
RepositoryURL: "https://github.com/git-chglog/git-chglog",
|
||||
},
|
||||
Options: &Options{
|
||||
Sort: "date",
|
||||
NextTag: "3.0.0",
|
||||
CommitFilters: map[string][]string{
|
||||
"Type": {
|
||||
"feat",
|
||||
},
|
||||
},
|
||||
CommitSortBy: "Scope",
|
||||
CommitGroupBy: "Type",
|
||||
CommitGroupSortBy: "Title",
|
||||
CommitGroupTitleMaps: map[string]string{
|
||||
"feat": "Features",
|
||||
},
|
||||
HeaderPattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$",
|
||||
HeaderPatternMaps: []string{
|
||||
"Type",
|
||||
"Scope",
|
||||
"Subject",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
err := gen.Generate(buf, "")
|
||||
expected := strings.TrimSpace(buf.String())
|
||||
|
||||
assert.Nil(err)
|
||||
assert.Equal(`My Changelog
|
||||
<a name="unreleased"></a>
|
||||
## [Unreleased]
|
||||
|
||||
|
||||
<a name="3.0.0"></a>
|
||||
## [3.0.0] - 2018-03-01
|
||||
### Features
|
||||
- **CORE:** version 3.0.0
|
||||
|
||||
|
||||
<a name="2.0.0"></a>
|
||||
## [2.0.0] - 2018-02-01
|
||||
### Features
|
||||
- **CORE:** version 2.0.0
|
||||
|
||||
|
||||
<a name="1.0.0"></a>
|
||||
## 1.0.0 - 2018-01-01
|
||||
### Features
|
||||
- **CORE:** version 1.0.0
|
||||
|
||||
|
||||
[Unreleased]: https://github.com/git-chglog/git-chglog/compare/3.0.0...HEAD
|
||||
[3.0.0]: https://github.com/git-chglog/git-chglog/compare/2.0.0...3.0.0
|
||||
[2.0.0]: https://github.com/git-chglog/git-chglog/compare/1.0.0...2.0.0`, expected)
|
||||
|
||||
* **core:** Add foo bar`, strings.TrimSpace(buf.String()))
|
||||
}
|
||||
|
|
|
|||
6
cmd/git-chglog/builder.go
Normal file
6
cmd/git-chglog/builder.go
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
package main
|
||||
|
||||
// Builder ...
|
||||
type Builder interface {
|
||||
Build(*Answer) (string, error)
|
||||
}
|
||||
|
|
@ -7,14 +7,15 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
|
||||
chglog "github.com/git-chglog/git-chglog"
|
||||
)
|
||||
|
||||
// CLI ...
|
||||
type CLI struct {
|
||||
ctx *Context
|
||||
ctx *CLIContext
|
||||
fs FileSystem
|
||||
logger *Logger
|
||||
logger *chglog.Logger
|
||||
configLoader ConfigLoader
|
||||
generator Generator
|
||||
processorFactory *ProcessorFactory
|
||||
|
|
@ -22,8 +23,7 @@ type CLI struct {
|
|||
|
||||
// NewCLI ...
|
||||
func NewCLI(
|
||||
ctx *Context,
|
||||
fs FileSystem,
|
||||
ctx *CLIContext, fs FileSystem,
|
||||
configLoader ConfigLoader,
|
||||
generator Generator,
|
||||
) *CLI {
|
||||
|
|
@ -35,7 +35,7 @@ func NewCLI(
|
|||
return &CLI{
|
||||
ctx: ctx,
|
||||
fs: fs,
|
||||
logger: NewLogger(ctx.Stdout, ctx.Stderr, silent, ctx.NoEmoji),
|
||||
logger: chglog.NewLogger(ctx.Stdout, ctx.Stderr, silent, ctx.NoEmoji),
|
||||
configLoader: configLoader,
|
||||
generator: generator,
|
||||
processorFactory: NewProcessorFactory(),
|
||||
|
|
@ -70,7 +70,7 @@ func (c *CLI) Run() int {
|
|||
return ExitCodeError
|
||||
}
|
||||
|
||||
err = c.generator.Generate(w, c.ctx.Query, changelogConfig)
|
||||
err = c.generator.Generate(c.logger, w, c.ctx.Query, changelogConfig)
|
||||
if err != nil {
|
||||
c.logger.Error(err.Error())
|
||||
return ExitCodeError
|
||||
|
|
|
|||
|
|
@ -4,10 +4,13 @@ import (
|
|||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
chglog "github.com/git-chglog/git-chglog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
chglog "github.com/git-chglog/git-chglog"
|
||||
)
|
||||
|
||||
func TestCLIForStdout(t *testing.T) {
|
||||
|
|
@ -34,13 +37,13 @@ func TestCLIForStdout(t *testing.T) {
|
|||
if config.Bin != "/custom/bin/git" {
|
||||
return errors.New("")
|
||||
}
|
||||
w.Write([]byte("success!!"))
|
||||
_, _ = w.Write([]byte("success!!"))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
c := NewCLI(
|
||||
&Context{
|
||||
&CLIContext{
|
||||
WorkingDir: "/",
|
||||
ConfigPath: "/.chglog/config.yml",
|
||||
OutputPath: "",
|
||||
|
|
@ -66,13 +69,13 @@ func TestCLIForFile(t *testing.T) {
|
|||
|
||||
mockFS := &mockFileSystem{
|
||||
ReturnMkdirP: func(path string) error {
|
||||
if path != "/dir/to" {
|
||||
if filepath.ToSlash(path) != "/dir/to" {
|
||||
return errors.New("")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
ReturnCreate: func(name string) (File, error) {
|
||||
if name != "/dir/to/CHANGELOG.tpl" {
|
||||
if filepath.ToSlash(name) != "/dir/to/CHANGELOG.tpl" {
|
||||
return nil, errors.New("")
|
||||
}
|
||||
return &mockFile{
|
||||
|
|
@ -88,7 +91,7 @@ func TestCLIForFile(t *testing.T) {
|
|||
|
||||
configLoader := &mockConfigLoaderImpl{
|
||||
ReturnLoad: func(path string) (*Config, error) {
|
||||
if path != "/.chglog/config.yml" {
|
||||
if filepath.ToSlash(path) != "/.chglog/config.yml" {
|
||||
return nil, errors.New("")
|
||||
}
|
||||
return &Config{
|
||||
|
|
@ -99,16 +102,16 @@ func TestCLIForFile(t *testing.T) {
|
|||
|
||||
generator := &mockGeneratorImpl{
|
||||
ReturnGenerate: func(w io.Writer, query string, config *chglog.Config) error {
|
||||
if config.Bin != "/custom/bin/git" {
|
||||
if filepath.ToSlash(config.Bin) != "/custom/bin/git" {
|
||||
return errors.New("")
|
||||
}
|
||||
w.Write([]byte("success!!"))
|
||||
_, _ = w.Write([]byte("success!!"))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
c := NewCLI(
|
||||
&Context{
|
||||
&CLIContext{
|
||||
WorkingDir: "/",
|
||||
ConfigPath: "/.chglog/config.yml",
|
||||
OutputPath: "/dir/to/CHANGELOG.tpl",
|
||||
|
|
@ -122,5 +125,6 @@ func TestCLIForFile(t *testing.T) {
|
|||
|
||||
assert.Equal(ExitCodeOK, c.Run())
|
||||
assert.Equal("", stderr.String())
|
||||
assert.Contains(stdout.String(), "Generate of \"/dir/to/CHANGELOG.tpl\"")
|
||||
out := regexp.MustCompile("\x1b\\[[^a-z]*[a-z]").ReplaceAllString(stdout.String(), "")
|
||||
assert.Contains(out, "Generate of \"/dir/to/CHANGELOG.tpl\"")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,9 @@ import (
|
|||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
chglog "github.com/git-chglog/git-chglog"
|
||||
"github.com/imdario/mergo"
|
||||
|
||||
chglog "github.com/git-chglog/git-chglog"
|
||||
)
|
||||
|
||||
// Info ...
|
||||
|
|
@ -22,9 +23,10 @@ type CommitOptions struct {
|
|||
|
||||
// CommitGroupOptions ...
|
||||
type CommitGroupOptions struct {
|
||||
GroupBy string `yaml:"group_by"`
|
||||
SortBy string `yaml:"sort_by"`
|
||||
TitleMaps map[string]string `yaml:"title_maps"`
|
||||
GroupBy string `yaml:"group_by"`
|
||||
SortBy string `yaml:"sort_by"`
|
||||
TitleOrder []string `yaml:"title_order"`
|
||||
TitleMaps map[string]string `yaml:"title_maps"`
|
||||
}
|
||||
|
||||
// PatternOptions ...
|
||||
|
|
@ -48,16 +50,38 @@ type NoteOptions struct {
|
|||
Keywords []string `yaml:"keywords"`
|
||||
}
|
||||
|
||||
// JiraClientInfoOptions ...
|
||||
type JiraClientInfoOptions struct {
|
||||
Username string `yaml:"username"`
|
||||
Token string `yaml:"token"`
|
||||
URL string `yaml:"url"`
|
||||
}
|
||||
|
||||
// JiraIssueOptions ...
|
||||
type JiraIssueOptions struct {
|
||||
TypeMaps map[string]string `yaml:"type_maps"`
|
||||
DescriptionPattern string `yaml:"description_pattern"`
|
||||
}
|
||||
|
||||
// JiraOptions ...
|
||||
type JiraOptions struct {
|
||||
ClintInfo JiraClientInfoOptions `yaml:"info"`
|
||||
Issue JiraIssueOptions `yaml:"issue"`
|
||||
}
|
||||
|
||||
// Options ...
|
||||
type Options struct {
|
||||
Commits CommitOptions `yaml:"commits"`
|
||||
CommitGroups CommitGroupOptions `yaml:"commit_groups"`
|
||||
Header PatternOptions `yaml:"header"`
|
||||
Issues IssueOptions `yaml:"issues"`
|
||||
Refs RefOptions `yaml:"refs"`
|
||||
Merges PatternOptions `yaml:"merges"`
|
||||
Reverts PatternOptions `yaml:"reverts"`
|
||||
Notes NoteOptions `yaml:"notes"`
|
||||
TagFilterPattern string `yaml:"tag_filter_pattern"`
|
||||
Sort string `yaml:"sort"`
|
||||
Commits CommitOptions `yaml:"commits"`
|
||||
CommitGroups CommitGroupOptions `yaml:"commit_groups"`
|
||||
Header PatternOptions `yaml:"header"`
|
||||
Issues IssueOptions `yaml:"issues"`
|
||||
Refs RefOptions `yaml:"refs"`
|
||||
Merges PatternOptions `yaml:"merges"`
|
||||
Reverts PatternOptions `yaml:"reverts"`
|
||||
Notes NoteOptions `yaml:"notes"`
|
||||
Jira JiraOptions `yaml:"jira"`
|
||||
}
|
||||
|
||||
// Config ...
|
||||
|
|
@ -70,7 +94,7 @@ type Config struct {
|
|||
}
|
||||
|
||||
// Normalize ...
|
||||
func (config *Config) Normalize(ctx *Context) error {
|
||||
func (config *Config) Normalize(ctx *CLIContext) error {
|
||||
err := mergo.Merge(config, &Config{
|
||||
Bin: "git",
|
||||
Template: "CHANGELOG.tpl.md",
|
||||
|
|
@ -99,6 +123,7 @@ func (config *Config) Normalize(ctx *Context) error {
|
|||
}
|
||||
|
||||
config.normalizeStyle()
|
||||
config.normalizeTagSortBy()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -108,6 +133,23 @@ func (config *Config) normalizeStyle() {
|
|||
switch config.Style {
|
||||
case "github":
|
||||
config.normalizeStyleOfGitHub()
|
||||
case "gitlab":
|
||||
config.normalizeStyleOfGitLab()
|
||||
case "bitbucket":
|
||||
config.normalizeStyleOfBitbucket()
|
||||
}
|
||||
}
|
||||
|
||||
func (config *Config) normalizeTagSortBy() {
|
||||
switch {
|
||||
case config.Options.Sort == "":
|
||||
config.Options.Sort = "date"
|
||||
case strings.EqualFold(config.Options.Sort, "date"):
|
||||
config.Options.Sort = "date"
|
||||
case strings.EqualFold(config.Options.Sort, "semver"):
|
||||
config.Options.Sort = "semver"
|
||||
default:
|
||||
config.Options.Sort = "date"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -147,34 +189,148 @@ func (config *Config) normalizeStyleOfGitHub() {
|
|||
config.Options = opts
|
||||
}
|
||||
|
||||
// For GitLab
|
||||
func (config *Config) normalizeStyleOfGitLab() {
|
||||
opts := config.Options
|
||||
|
||||
if len(opts.Issues.Prefix) == 0 {
|
||||
opts.Issues.Prefix = []string{
|
||||
"#",
|
||||
}
|
||||
}
|
||||
|
||||
if len(opts.Refs.Actions) == 0 {
|
||||
opts.Refs.Actions = []string{
|
||||
"close",
|
||||
"closes",
|
||||
"closed",
|
||||
"closing",
|
||||
"fix",
|
||||
"fixes",
|
||||
"fixed",
|
||||
"fixing",
|
||||
"resolve",
|
||||
"resolves",
|
||||
"resolved",
|
||||
"resolving",
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Merges.Pattern == "" && len(opts.Merges.PatternMaps) == 0 {
|
||||
opts.Merges.Pattern = "^Merge branch '.*' into '(.*)'$"
|
||||
opts.Merges.PatternMaps = []string{
|
||||
"Source",
|
||||
}
|
||||
}
|
||||
|
||||
config.Options = opts
|
||||
}
|
||||
|
||||
// For Bitbucket
|
||||
func (config *Config) normalizeStyleOfBitbucket() {
|
||||
opts := config.Options
|
||||
|
||||
if len(opts.Issues.Prefix) == 0 {
|
||||
opts.Issues.Prefix = []string{
|
||||
"#",
|
||||
}
|
||||
}
|
||||
|
||||
if len(opts.Refs.Actions) == 0 {
|
||||
opts.Refs.Actions = []string{
|
||||
"close",
|
||||
"closes",
|
||||
"closed",
|
||||
"closing",
|
||||
"fix",
|
||||
"fixed",
|
||||
"fixes",
|
||||
"fixing",
|
||||
"resolve",
|
||||
"resolves",
|
||||
"resolved",
|
||||
"resolving",
|
||||
"eopen",
|
||||
"reopens",
|
||||
"reopening",
|
||||
"hold",
|
||||
"holds",
|
||||
"holding",
|
||||
"wontfix",
|
||||
"invalidate",
|
||||
"invalidates",
|
||||
"invalidated",
|
||||
"invalidating",
|
||||
"addresses",
|
||||
"re",
|
||||
"references",
|
||||
"ref",
|
||||
"refs",
|
||||
"see",
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Merges.Pattern == "" && len(opts.Merges.PatternMaps) == 0 {
|
||||
opts.Merges.Pattern = "^Merged in (.*) \\(pull request #(\\d+)\\)$"
|
||||
opts.Merges.PatternMaps = []string{
|
||||
"Source",
|
||||
"Ref",
|
||||
}
|
||||
}
|
||||
|
||||
config.Options = opts
|
||||
}
|
||||
|
||||
func orValue(str1 string, str2 string) string {
|
||||
if str1 != "" {
|
||||
return str1
|
||||
}
|
||||
return str2
|
||||
}
|
||||
|
||||
// Convert ...
|
||||
func (config *Config) Convert(ctx *Context) *chglog.Config {
|
||||
func (config *Config) Convert(ctx *CLIContext) *chglog.Config {
|
||||
info := config.Info
|
||||
opts := config.Options
|
||||
|
||||
if ctx.TagFilterPattern == "" {
|
||||
ctx.TagFilterPattern = opts.TagFilterPattern
|
||||
}
|
||||
|
||||
return &chglog.Config{
|
||||
Bin: config.Bin,
|
||||
WorkingDir: ctx.WorkingDir,
|
||||
Template: config.Template,
|
||||
Template: orValue(ctx.Template, config.Template),
|
||||
Info: &chglog.Info{
|
||||
Title: info.Title,
|
||||
RepositoryURL: info.RepositoryURL,
|
||||
RepositoryURL: orValue(ctx.RepositoryURL, info.RepositoryURL),
|
||||
},
|
||||
Options: &chglog.Options{
|
||||
CommitFilters: opts.Commits.Filters,
|
||||
CommitSortBy: opts.Commits.SortBy,
|
||||
CommitGroupBy: opts.CommitGroups.GroupBy,
|
||||
CommitGroupSortBy: opts.CommitGroups.SortBy,
|
||||
CommitGroupTitleMaps: opts.CommitGroups.TitleMaps,
|
||||
HeaderPattern: opts.Header.Pattern,
|
||||
HeaderPatternMaps: opts.Header.PatternMaps,
|
||||
IssuePrefix: opts.Issues.Prefix,
|
||||
RefActions: opts.Refs.Actions,
|
||||
MergePattern: opts.Merges.Pattern,
|
||||
MergePatternMaps: opts.Merges.PatternMaps,
|
||||
RevertPattern: opts.Reverts.Pattern,
|
||||
RevertPatternMaps: opts.Reverts.PatternMaps,
|
||||
NoteKeywords: opts.Notes.Keywords,
|
||||
NextTag: ctx.NextTag,
|
||||
TagFilterPattern: ctx.TagFilterPattern,
|
||||
Sort: orValue(ctx.Sort, opts.Sort),
|
||||
NoCaseSensitive: ctx.NoCaseSensitive,
|
||||
Paths: ctx.Paths,
|
||||
CommitFilters: opts.Commits.Filters,
|
||||
CommitSortBy: opts.Commits.SortBy,
|
||||
CommitGroupBy: opts.CommitGroups.GroupBy,
|
||||
CommitGroupSortBy: opts.CommitGroups.SortBy,
|
||||
CommitGroupTitleMaps: opts.CommitGroups.TitleMaps,
|
||||
CommitGroupTitleOrder: opts.CommitGroups.TitleOrder,
|
||||
HeaderPattern: opts.Header.Pattern,
|
||||
HeaderPatternMaps: opts.Header.PatternMaps,
|
||||
IssuePrefix: opts.Issues.Prefix,
|
||||
RefActions: opts.Refs.Actions,
|
||||
MergePattern: opts.Merges.Pattern,
|
||||
MergePatternMaps: opts.Merges.PatternMaps,
|
||||
RevertPattern: opts.Reverts.Pattern,
|
||||
RevertPatternMaps: opts.Reverts.PatternMaps,
|
||||
NoteKeywords: opts.Notes.Keywords,
|
||||
JiraUsername: orValue(ctx.JiraUsername, opts.Jira.ClintInfo.Username),
|
||||
JiraToken: orValue(ctx.JiraToken, opts.Jira.ClintInfo.Token),
|
||||
JiraURL: orValue(ctx.JiraURL, opts.Jira.ClintInfo.URL),
|
||||
JiraTypeMaps: opts.Jira.Issue.TypeMaps,
|
||||
JiraIssueDescriptionPattern: opts.Jira.Issue.DescriptionPattern,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
68
cmd/git-chglog/config_builder.go
Normal file
68
cmd/git-chglog/config_builder.go
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ConfigBuilder ...
|
||||
type ConfigBuilder interface {
|
||||
Builder
|
||||
}
|
||||
|
||||
type configBuilderImpl struct{}
|
||||
|
||||
// NewConfigBuilder ...
|
||||
func NewConfigBuilder() ConfigBuilder {
|
||||
return &configBuilderImpl{}
|
||||
}
|
||||
|
||||
// Build ...
|
||||
func (*configBuilderImpl) Build(ans *Answer) (string, error) {
|
||||
var msgFormat *CommitMessageFormat
|
||||
|
||||
for _, ff := range formats {
|
||||
f, _ := ff.(*CommitMessageFormat)
|
||||
if f.display == ans.CommitMessageFormat {
|
||||
msgFormat = f
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if msgFormat == nil {
|
||||
return "", fmt.Errorf("\"%s\" is an invalid commit message format", ans.CommitMessageFormat)
|
||||
}
|
||||
|
||||
repoURL := strings.TrimRight(ans.RepositoryURL, "/")
|
||||
if repoURL == "" {
|
||||
repoURL = "\"\""
|
||||
}
|
||||
|
||||
config := fmt.Sprintf(`style: %s
|
||||
template: %s
|
||||
info:
|
||||
title: CHANGELOG
|
||||
repository_url: %s
|
||||
options:
|
||||
commits:
|
||||
# filters:
|
||||
# Type:%s
|
||||
commit_groups:
|
||||
# title_maps:%s
|
||||
header:
|
||||
pattern: "%s"
|
||||
pattern_maps:%s
|
||||
notes:
|
||||
keywords:
|
||||
- BREAKING CHANGE`,
|
||||
ans.Style,
|
||||
defaultTemplateFilename,
|
||||
repoURL,
|
||||
msgFormat.FilterTypesString(),
|
||||
msgFormat.TitleMapsString(),
|
||||
msgFormat.pattern,
|
||||
msgFormat.PatternMapString(),
|
||||
)
|
||||
|
||||
return config, nil
|
||||
}
|
||||
9
cmd/git-chglog/config_builder_mock.go
Normal file
9
cmd/git-chglog/config_builder_mock.go
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
package main
|
||||
|
||||
type mockConfigBuilderImpl struct {
|
||||
ReturnBuild func(*Answer) (string, error)
|
||||
}
|
||||
|
||||
func (m *mockConfigBuilderImpl) Build(ans *Answer) (string, error) {
|
||||
return m.ReturnBuild(ans)
|
||||
}
|
||||
62
cmd/git-chglog/config_builder_test.go
Normal file
62
cmd/git-chglog/config_builder_test.go
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestConfigBulider(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
builder := NewConfigBuilder()
|
||||
|
||||
out, err := builder.Build(&Answer{
|
||||
RepositoryURL: "https://github.com/git-chglog/git-chglog/git-chglog/",
|
||||
Style: styleNone,
|
||||
CommitMessageFormat: fmtGitBasic.display,
|
||||
Template: tplStandard.display,
|
||||
})
|
||||
|
||||
assert.Nil(err)
|
||||
assert.Contains(out, "style: none")
|
||||
assert.Contains(out, "template: CHANGELOG.tpl.md")
|
||||
assert.Contains(out, " repository_url: https://github.com/git-chglog/git-chglog/git-chglog")
|
||||
assert.Contains(out, fmt.Sprintf(" pattern: \"%s\"", fmtGitBasic.pattern))
|
||||
assert.Contains(out, fmt.Sprintf(
|
||||
` pattern_maps:
|
||||
- %s
|
||||
- %s`,
|
||||
fmtGitBasic.patternMaps[0],
|
||||
fmtGitBasic.patternMaps[1],
|
||||
))
|
||||
}
|
||||
|
||||
func TestConfigBuliderEmptyRepoURL(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
builder := NewConfigBuilder()
|
||||
|
||||
out, err := builder.Build(&Answer{
|
||||
RepositoryURL: "",
|
||||
Style: styleNone,
|
||||
CommitMessageFormat: fmtGitBasic.display,
|
||||
Template: tplStandard.display,
|
||||
})
|
||||
|
||||
assert.Nil(err)
|
||||
assert.Contains(out, " repository_url: \"\"")
|
||||
}
|
||||
|
||||
func TestConfigBuliderInvalidFormat(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
builder := NewConfigBuilder()
|
||||
|
||||
_, err := builder.Build(&Answer{
|
||||
RepositoryURL: "",
|
||||
Style: styleNone,
|
||||
CommitMessageFormat: "",
|
||||
Template: tplStandard.display,
|
||||
})
|
||||
|
||||
assert.Contains(err.Error(), "invalid commit message format")
|
||||
}
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// ConfigLoader ...
|
||||
|
|
@ -20,7 +21,8 @@ func NewConfigLoader() ConfigLoader {
|
|||
}
|
||||
|
||||
func (loader *configLoaderImpl) Load(path string) (*Config, error) {
|
||||
bytes, err := ioutil.ReadFile(path)
|
||||
fp := filepath.Clean(path)
|
||||
bytes, err := os.ReadFile(fp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
|
@ -16,24 +18,53 @@ func TestConfigNormalize(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
err := config.Normalize(&Context{
|
||||
ConfigPath: "/test/config.yml",
|
||||
err := config.Normalize(&CLIContext{
|
||||
ConfigPath: filepath.FromSlash("/test/config.yml"),
|
||||
})
|
||||
|
||||
assert.Nil(err)
|
||||
assert.Equal("git", config.Bin)
|
||||
assert.Equal("https://example.com/foo/bar", config.Info.RepositoryURL)
|
||||
assert.Equal("/test/CHANGELOG.tpl.md", config.Template)
|
||||
assert.Equal("/test/CHANGELOG.tpl.md", filepath.ToSlash(config.Template))
|
||||
|
||||
// abs template
|
||||
cwd, _ := os.Getwd()
|
||||
|
||||
config = &Config{
|
||||
Template: "/CHANGELOG.tpl.md",
|
||||
Template: filepath.Join(cwd, "CHANGELOG.tpl.md"),
|
||||
}
|
||||
|
||||
err = config.Normalize(&Context{
|
||||
ConfigPath: "/test/config.yml",
|
||||
err = config.Normalize(&CLIContext{
|
||||
ConfigPath: filepath.Join(cwd, "test", "config.yml"),
|
||||
})
|
||||
|
||||
assert.Nil(err)
|
||||
assert.Equal("/CHANGELOG.tpl.md", config.Template)
|
||||
assert.Equal(filepath.Join(cwd, "CHANGELOG.tpl.md"), config.Template)
|
||||
}
|
||||
|
||||
func TestConfigConvert(t *testing.T) {
|
||||
var patternInFile = "pattern in config"
|
||||
var patternInArgs = "pattern in cli"
|
||||
assert := assert.New(t)
|
||||
// basic
|
||||
config := &Config{
|
||||
Info: Info{
|
||||
RepositoryURL: "https://example.com/foo/bar/",
|
||||
},
|
||||
Options: Options{TagFilterPattern: patternInFile},
|
||||
}
|
||||
cli := &CLIContext{TagFilterPattern: patternInArgs}
|
||||
cfg := config.Convert(cli)
|
||||
assert.Equal(cfg.Options.TagFilterPattern, patternInArgs)
|
||||
|
||||
config = &Config{
|
||||
Info: Info{
|
||||
RepositoryURL: "https://example.com/foo/bar/",
|
||||
},
|
||||
Options: Options{TagFilterPattern: patternInFile},
|
||||
}
|
||||
cli = &CLIContext{}
|
||||
cfg = config.Convert(cli)
|
||||
assert.Equal(cfg.Options.TagFilterPattern, patternInFile)
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,15 +4,32 @@ import (
|
|||
"io"
|
||||
)
|
||||
|
||||
// Context ...
|
||||
type Context struct {
|
||||
// CLIContext ...
|
||||
type CLIContext struct {
|
||||
WorkingDir string
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
ConfigPath string
|
||||
Template string
|
||||
RepositoryURL string
|
||||
OutputPath string
|
||||
Silent bool
|
||||
NoColor bool
|
||||
NoEmoji bool
|
||||
NoCaseSensitive bool
|
||||
Query string
|
||||
NextTag string
|
||||
TagFilterPattern string
|
||||
JiraUsername string
|
||||
JiraToken string
|
||||
JiraURL string
|
||||
Paths []string
|
||||
Sort string
|
||||
}
|
||||
|
||||
// InitContext ...
|
||||
type InitContext struct {
|
||||
WorkingDir string
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
ConfigPath string
|
||||
OutputPath string
|
||||
Silent bool
|
||||
NoColor bool
|
||||
NoEmoji bool
|
||||
Query string
|
||||
}
|
||||
|
|
|
|||
157
cmd/git-chglog/custom_template_builder.go
Normal file
157
cmd/git-chglog/custom_template_builder.go
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type customTemplateBuilderImpl struct{}
|
||||
|
||||
// NewCustomTemplateBuilder ...
|
||||
func NewCustomTemplateBuilder() TemplateBuilder {
|
||||
return &customTemplateBuilderImpl{}
|
||||
}
|
||||
|
||||
// Build ...
|
||||
func (t *customTemplateBuilderImpl) Build(ans *Answer) (string, error) {
|
||||
// versions
|
||||
tpl := "{{ range .Versions }}\n"
|
||||
|
||||
// version header
|
||||
tpl += t.versionHeader(ans.Style, ans.Template)
|
||||
|
||||
// commits
|
||||
tpl += t.commits(ans.CommitMessageFormat)
|
||||
|
||||
// revert
|
||||
if ans.IncludeReverts {
|
||||
tpl += t.reverts()
|
||||
}
|
||||
|
||||
// merges
|
||||
if ans.IncludeMerges {
|
||||
tpl += t.merges(ans.Style)
|
||||
}
|
||||
|
||||
// notes
|
||||
tpl += t.notes()
|
||||
|
||||
// versions end
|
||||
tpl += "{{ end -}}"
|
||||
|
||||
return tpl, nil
|
||||
}
|
||||
|
||||
func (*customTemplateBuilderImpl) versionHeader(style, template string) string {
|
||||
var (
|
||||
tpl string
|
||||
tagName = "{{ .Tag.Name }}"
|
||||
date = "{{ datetime \"2006-01-02\" .Tag.Date }}"
|
||||
)
|
||||
|
||||
// parts
|
||||
switch style {
|
||||
case styleGitHub, styleGitLab:
|
||||
tpl = templateTagNameAnchor
|
||||
tagName = "{{ if .Tag.Previous }}[{{ .Tag.Name }}]({{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }}){{ else }}{{ .Tag.Name }}{{ end }}"
|
||||
case styleBitbucket:
|
||||
tpl = templateTagNameAnchor
|
||||
tagName = "{{ if .Tag.Previous }}[{{ .Tag.Name }}]({{ $.Info.RepositoryURL }}/compare/{{ .Tag.Name }}..{{ .Tag.Previous.Name }}){{ else }}{{ .Tag.Name }}{{ end }}"
|
||||
}
|
||||
|
||||
// format
|
||||
switch template {
|
||||
case tplStandard.display:
|
||||
tpl = fmt.Sprintf("%s## %s (%s)\n\n",
|
||||
tpl,
|
||||
tagName,
|
||||
date,
|
||||
)
|
||||
case tplCool.display:
|
||||
tpl = fmt.Sprintf("%s## %s\n\n> %s\n\n",
|
||||
tpl,
|
||||
tagName,
|
||||
date,
|
||||
)
|
||||
}
|
||||
|
||||
return tpl
|
||||
}
|
||||
|
||||
func (*customTemplateBuilderImpl) commits(format string) string {
|
||||
var (
|
||||
header string
|
||||
body string
|
||||
)
|
||||
|
||||
switch format {
|
||||
case fmtSubject.display:
|
||||
body = `{{ range .Commits -}}
|
||||
* {{ .Header }}
|
||||
{{ end }}`
|
||||
|
||||
default:
|
||||
if format == fmtTypeScopeSubject.display {
|
||||
header = "{{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}"
|
||||
} else {
|
||||
header = "{{ .Subject }}"
|
||||
}
|
||||
|
||||
body = fmt.Sprintf(`### {{ .Title }}
|
||||
|
||||
{{ range .Commits -}}
|
||||
* %s
|
||||
{{ end }}`, header)
|
||||
}
|
||||
|
||||
return fmt.Sprintf(`{{ range .CommitGroups -}}
|
||||
%s
|
||||
{{ end -}}
|
||||
`, body)
|
||||
}
|
||||
|
||||
func (*customTemplateBuilderImpl) reverts() string {
|
||||
return `
|
||||
{{- if .RevertCommits -}}
|
||||
### Reverts
|
||||
|
||||
{{ range .RevertCommits -}}
|
||||
* {{ .Revert.Header }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
`
|
||||
}
|
||||
|
||||
func (t *customTemplateBuilderImpl) merges(style string) string {
|
||||
var title string
|
||||
|
||||
switch style {
|
||||
case styleGitHub, styleBitbucket:
|
||||
title = "Pull Requests"
|
||||
case styleGitLab:
|
||||
title = "Merge Requests"
|
||||
default:
|
||||
title = "Merges"
|
||||
}
|
||||
|
||||
return fmt.Sprintf(`
|
||||
{{- if .MergeCommits -}}
|
||||
### %s
|
||||
|
||||
{{ range .MergeCommits -}}
|
||||
* {{ .Header }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
`, title)
|
||||
}
|
||||
|
||||
func (*customTemplateBuilderImpl) notes() string {
|
||||
return `
|
||||
{{- if .NoteGroups -}}
|
||||
{{ range .NoteGroups -}}
|
||||
### {{ .Title }}
|
||||
|
||||
{{ range .Notes }}
|
||||
{{ .Body }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
`
|
||||
}
|
||||
356
cmd/git-chglog/custom_template_builder_test.go
Normal file
356
cmd/git-chglog/custom_template_builder_test.go
Normal file
|
|
@ -0,0 +1,356 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCustomTemplateBuilderDefault(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
builder := NewCustomTemplateBuilder()
|
||||
|
||||
out, err := builder.Build(&Answer{
|
||||
Style: styleGitHub,
|
||||
CommitMessageFormat: fmtTypeScopeSubject.display,
|
||||
Template: tplStandard.display,
|
||||
IncludeMerges: true,
|
||||
IncludeReverts: true,
|
||||
})
|
||||
|
||||
assert.Nil(err)
|
||||
assert.Equal(`{{ range .Versions }}
|
||||
<a name="{{ .Tag.Name }}"></a>
|
||||
## {{ if .Tag.Previous }}[{{ .Tag.Name }}]({{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }}){{ else }}{{ .Tag.Name }}{{ end }} ({{ datetime "2006-01-02" .Tag.Date }})
|
||||
|
||||
{{ range .CommitGroups -}}
|
||||
### {{ .Title }}
|
||||
|
||||
{{ range .Commits -}}
|
||||
* {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .RevertCommits -}}
|
||||
### Reverts
|
||||
|
||||
{{ range .RevertCommits -}}
|
||||
* {{ .Revert.Header }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .MergeCommits -}}
|
||||
### Pull Requests
|
||||
|
||||
{{ range .MergeCommits -}}
|
||||
* {{ .Header }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .NoteGroups -}}
|
||||
{{ range .NoteGroups -}}
|
||||
### {{ .Title }}
|
||||
|
||||
{{ range .Notes }}
|
||||
{{ .Body }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}`, out)
|
||||
}
|
||||
|
||||
func TestCustomTemplateBuilderNone(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
builder := NewCustomTemplateBuilder()
|
||||
|
||||
out, err := builder.Build(&Answer{
|
||||
Style: styleNone,
|
||||
CommitMessageFormat: fmtTypeScopeSubject.display,
|
||||
Template: tplStandard.display,
|
||||
IncludeMerges: true,
|
||||
IncludeReverts: true,
|
||||
})
|
||||
|
||||
assert.Nil(err)
|
||||
assert.Equal(`{{ range .Versions }}
|
||||
## {{ .Tag.Name }} ({{ datetime "2006-01-02" .Tag.Date }})
|
||||
|
||||
{{ range .CommitGroups -}}
|
||||
### {{ .Title }}
|
||||
|
||||
{{ range .Commits -}}
|
||||
* {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .RevertCommits -}}
|
||||
### Reverts
|
||||
|
||||
{{ range .RevertCommits -}}
|
||||
* {{ .Revert.Header }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .MergeCommits -}}
|
||||
### Merges
|
||||
|
||||
{{ range .MergeCommits -}}
|
||||
* {{ .Header }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .NoteGroups -}}
|
||||
{{ range .NoteGroups -}}
|
||||
### {{ .Title }}
|
||||
|
||||
{{ range .Notes }}
|
||||
{{ .Body }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}`, out)
|
||||
}
|
||||
|
||||
func TestCustomTemplateBuilderSubjectOnly(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
builder := NewCustomTemplateBuilder()
|
||||
|
||||
out, err := builder.Build(&Answer{
|
||||
Style: styleNone,
|
||||
CommitMessageFormat: fmtSubject.display,
|
||||
Template: tplStandard.display,
|
||||
IncludeMerges: true,
|
||||
IncludeReverts: true,
|
||||
})
|
||||
|
||||
assert.Nil(err)
|
||||
assert.Equal(`{{ range .Versions }}
|
||||
## {{ .Tag.Name }} ({{ datetime "2006-01-02" .Tag.Date }})
|
||||
|
||||
{{ range .CommitGroups -}}
|
||||
{{ range .Commits -}}
|
||||
* {{ .Header }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .RevertCommits -}}
|
||||
### Reverts
|
||||
|
||||
{{ range .RevertCommits -}}
|
||||
* {{ .Revert.Header }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .MergeCommits -}}
|
||||
### Merges
|
||||
|
||||
{{ range .MergeCommits -}}
|
||||
* {{ .Header }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .NoteGroups -}}
|
||||
{{ range .NoteGroups -}}
|
||||
### {{ .Title }}
|
||||
|
||||
{{ range .Notes }}
|
||||
{{ .Body }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}`, out)
|
||||
}
|
||||
|
||||
func TestCustomTemplateBuilderSubject(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
builder := NewCustomTemplateBuilder()
|
||||
|
||||
out, err := builder.Build(&Answer{
|
||||
Style: styleNone,
|
||||
CommitMessageFormat: fmtTypeSubject.display,
|
||||
Template: tplStandard.display,
|
||||
IncludeMerges: true,
|
||||
IncludeReverts: true,
|
||||
})
|
||||
|
||||
assert.Nil(err)
|
||||
assert.Equal(`{{ range .Versions }}
|
||||
## {{ .Tag.Name }} ({{ datetime "2006-01-02" .Tag.Date }})
|
||||
|
||||
{{ range .CommitGroups -}}
|
||||
### {{ .Title }}
|
||||
|
||||
{{ range .Commits -}}
|
||||
* {{ .Subject }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .RevertCommits -}}
|
||||
### Reverts
|
||||
|
||||
{{ range .RevertCommits -}}
|
||||
* {{ .Revert.Header }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .MergeCommits -}}
|
||||
### Merges
|
||||
|
||||
{{ range .MergeCommits -}}
|
||||
* {{ .Header }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .NoteGroups -}}
|
||||
{{ range .NoteGroups -}}
|
||||
### {{ .Title }}
|
||||
|
||||
{{ range .Notes }}
|
||||
{{ .Body }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}`, out)
|
||||
}
|
||||
|
||||
func TestCustomTemplateBuilderIgnoreReverts(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
builder := NewCustomTemplateBuilder()
|
||||
|
||||
out, err := builder.Build(&Answer{
|
||||
Style: styleNone,
|
||||
CommitMessageFormat: fmtTypeSubject.display,
|
||||
Template: tplStandard.display,
|
||||
IncludeMerges: true,
|
||||
IncludeReverts: false,
|
||||
})
|
||||
|
||||
assert.Nil(err)
|
||||
assert.Equal(`{{ range .Versions }}
|
||||
## {{ .Tag.Name }} ({{ datetime "2006-01-02" .Tag.Date }})
|
||||
|
||||
{{ range .CommitGroups -}}
|
||||
### {{ .Title }}
|
||||
|
||||
{{ range .Commits -}}
|
||||
* {{ .Subject }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .MergeCommits -}}
|
||||
### Merges
|
||||
|
||||
{{ range .MergeCommits -}}
|
||||
* {{ .Header }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .NoteGroups -}}
|
||||
{{ range .NoteGroups -}}
|
||||
### {{ .Title }}
|
||||
|
||||
{{ range .Notes }}
|
||||
{{ .Body }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}`, out)
|
||||
}
|
||||
|
||||
func TestCustomTemplateBuilderIgnoreMerges(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
builder := NewCustomTemplateBuilder()
|
||||
|
||||
out, err := builder.Build(&Answer{
|
||||
Style: styleNone,
|
||||
CommitMessageFormat: fmtTypeSubject.display,
|
||||
Template: tplStandard.display,
|
||||
IncludeMerges: false,
|
||||
IncludeReverts: true,
|
||||
})
|
||||
|
||||
assert.Nil(err)
|
||||
assert.Equal(`{{ range .Versions }}
|
||||
## {{ .Tag.Name }} ({{ datetime "2006-01-02" .Tag.Date }})
|
||||
|
||||
{{ range .CommitGroups -}}
|
||||
### {{ .Title }}
|
||||
|
||||
{{ range .Commits -}}
|
||||
* {{ .Subject }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .RevertCommits -}}
|
||||
### Reverts
|
||||
|
||||
{{ range .RevertCommits -}}
|
||||
* {{ .Revert.Header }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .NoteGroups -}}
|
||||
{{ range .NoteGroups -}}
|
||||
### {{ .Title }}
|
||||
|
||||
{{ range .Notes }}
|
||||
{{ .Body }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}`, out)
|
||||
}
|
||||
|
||||
func TestCustomTemplateBuilderCool(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
builder := NewCustomTemplateBuilder()
|
||||
|
||||
out, err := builder.Build(&Answer{
|
||||
Style: styleNone,
|
||||
CommitMessageFormat: fmtTypeScopeSubject.display,
|
||||
Template: tplCool.display,
|
||||
IncludeMerges: true,
|
||||
IncludeReverts: true,
|
||||
})
|
||||
|
||||
assert.Nil(err)
|
||||
assert.Equal(`{{ range .Versions }}
|
||||
## {{ .Tag.Name }}
|
||||
|
||||
> {{ datetime "2006-01-02" .Tag.Date }}
|
||||
|
||||
{{ range .CommitGroups -}}
|
||||
### {{ .Title }}
|
||||
|
||||
{{ range .Commits -}}
|
||||
* {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .RevertCommits -}}
|
||||
### Reverts
|
||||
|
||||
{{ range .RevertCommits -}}
|
||||
* {{ .Revert.Header }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .MergeCommits -}}
|
||||
### Merges
|
||||
|
||||
{{ range .MergeCommits -}}
|
||||
* {{ .Header }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .NoteGroups -}}
|
||||
{{ range .NoteGroups -}}
|
||||
### {{ .Title }}
|
||||
|
||||
{{ range .Notes }}
|
||||
{{ .Body }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}`, out)
|
||||
}
|
||||
|
|
@ -7,8 +7,10 @@ import (
|
|||
|
||||
// FileSystem ...
|
||||
type FileSystem interface {
|
||||
Exists(path string) bool
|
||||
MkdirP(path string) error
|
||||
Create(name string) (File, error)
|
||||
WriteFile(path string, content []byte) error
|
||||
}
|
||||
|
||||
// File ...
|
||||
|
|
@ -25,13 +27,25 @@ var fs = &osFileSystem{}
|
|||
|
||||
type osFileSystem struct{}
|
||||
|
||||
func (*osFileSystem) Exists(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (*osFileSystem) MkdirP(path string) error {
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
//nolint:gosec
|
||||
return os.MkdirAll(path, os.ModePerm)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*osFileSystem) Create(name string) (File, error) {
|
||||
//nolint: gosec
|
||||
return os.Create(name)
|
||||
}
|
||||
|
||||
func (*osFileSystem) WriteFile(path string, content []byte) error {
|
||||
//nolint:gosec
|
||||
return os.WriteFile(path, content, os.ModePerm)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,14 @@
|
|||
package main
|
||||
|
||||
type mockFileSystem struct {
|
||||
ReturnMkdirP func(string) error
|
||||
ReturnCreate func(string) (File, error)
|
||||
ReturnExists func(string) bool
|
||||
ReturnMkdirP func(string) error
|
||||
ReturnCreate func(string) (File, error)
|
||||
ReturnWriteFile func(string, []byte) error
|
||||
}
|
||||
|
||||
func (m *mockFileSystem) Exists(path string) bool {
|
||||
return m.ReturnExists(path)
|
||||
}
|
||||
|
||||
func (m *mockFileSystem) MkdirP(path string) error {
|
||||
|
|
@ -13,6 +19,10 @@ func (m *mockFileSystem) Create(name string) (File, error) {
|
|||
return m.ReturnCreate(name)
|
||||
}
|
||||
|
||||
func (m *mockFileSystem) WriteFile(path string, content []byte) error {
|
||||
return m.ReturnWriteFile(path, content)
|
||||
}
|
||||
|
||||
type mockFile struct {
|
||||
File
|
||||
ReturnWrite func([]byte) (int, error)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
|
||||
// Generator ...
|
||||
type Generator interface {
|
||||
Generate(io.Writer, string, *chglog.Config) error
|
||||
Generate(*chglog.Logger, io.Writer, string, *chglog.Config) error
|
||||
}
|
||||
|
||||
type generatorImpl struct{}
|
||||
|
|
@ -19,6 +19,6 @@ func NewGenerator() Generator {
|
|||
}
|
||||
|
||||
// Generate ...
|
||||
func (*generatorImpl) Generate(w io.Writer, query string, config *chglog.Config) error {
|
||||
return chglog.NewGenerator(config).Generate(w, query)
|
||||
func (*generatorImpl) Generate(logger *chglog.Logger, w io.Writer, query string, config *chglog.Config) error {
|
||||
return chglog.NewGenerator(logger, config).Generate(w, query)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,6 @@ type mockGeneratorImpl struct {
|
|||
ReturnGenerate func(io.Writer, string, *chglog.Config) error
|
||||
}
|
||||
|
||||
func (m *mockGeneratorImpl) Generate(w io.Writer, query string, config *chglog.Config) error {
|
||||
func (m *mockGeneratorImpl) Generate(logger *chglog.Logger, w io.Writer, query string, config *chglog.Config) error {
|
||||
return m.ReturnGenerate(w, query, config)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,412 +1,95 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/fatih/color"
|
||||
gitcmd "github.com/tsuyoshiwada/go-gitcmd"
|
||||
survey "gopkg.in/AlecAivazis/survey.v1"
|
||||
emoji "gopkg.in/kyokomi/emoji.v1"
|
||||
|
||||
chglog "github.com/git-chglog/git-chglog"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultConfigFilename = "config.yml"
|
||||
defaultTemplateFilename = "CHANGELOG.tpl.md"
|
||||
|
||||
styleGitHub = "github"
|
||||
styleNone = "none"
|
||||
changelogStyles = []string{
|
||||
styleGitHub,
|
||||
styleNone,
|
||||
}
|
||||
|
||||
fmtTypeScopeSubject = "<type>(<scope>): <subject>"
|
||||
fmtTypeSubject = "<type>: <subject>"
|
||||
fmtSubject = "<subject>"
|
||||
commitMessageFormats = []string{
|
||||
fmtTypeScopeSubject,
|
||||
fmtTypeSubject,
|
||||
fmtSubject,
|
||||
}
|
||||
|
||||
tplStandard = "standard"
|
||||
tplCool = "cool"
|
||||
templateStyles = []string{
|
||||
tplStandard,
|
||||
tplCool,
|
||||
}
|
||||
)
|
||||
|
||||
// Answer ...
|
||||
type Answer struct {
|
||||
RepositoryURL string `survey:"repository_url"`
|
||||
Style string `survey:"style"`
|
||||
CommitMessageFormat string `survey:"commit_message_format"`
|
||||
Template string `survey:"template"`
|
||||
IncludeMerges bool `survey:"include_merges"`
|
||||
IncludeReverts bool `survey:"include_reverts"`
|
||||
ConfigDir string `survey:"config_dir"`
|
||||
}
|
||||
|
||||
// Initializer ...
|
||||
type Initializer struct {
|
||||
client gitcmd.Client
|
||||
ctx *InitContext
|
||||
fs FileSystem
|
||||
logger *chglog.Logger
|
||||
questioner Questioner
|
||||
configBuilder ConfigBuilder
|
||||
templateBuilderFactory TemplateBuilderFactory
|
||||
}
|
||||
|
||||
// NewInitializer ...
|
||||
func NewInitializer() *Initializer {
|
||||
func NewInitializer(
|
||||
ctx *InitContext,
|
||||
fs FileSystem,
|
||||
questioner Questioner,
|
||||
configBuilder ConfigBuilder,
|
||||
tplBuilderFactory TemplateBuilderFactory) *Initializer {
|
||||
return &Initializer{
|
||||
client: gitcmd.New(&gitcmd.Config{
|
||||
Bin: "git",
|
||||
}),
|
||||
ctx: ctx,
|
||||
fs: fs,
|
||||
logger: chglog.NewLogger(ctx.Stdout, ctx.Stderr, false, false),
|
||||
questioner: questioner,
|
||||
configBuilder: configBuilder,
|
||||
templateBuilderFactory: tplBuilderFactory,
|
||||
}
|
||||
}
|
||||
|
||||
// Run ...
|
||||
func (init *Initializer) Run() int {
|
||||
answer, err := init.ask()
|
||||
ans, err := init.questioner.Ask()
|
||||
if err != nil {
|
||||
init.logger.Error(err.Error())
|
||||
return ExitCodeError
|
||||
}
|
||||
|
||||
err = init.generateConfigure(answer)
|
||||
if err != nil {
|
||||
if err = init.fs.MkdirP(filepath.Join(init.ctx.WorkingDir, ans.ConfigDir)); err != nil {
|
||||
init.logger.Error(err.Error())
|
||||
return ExitCodeError
|
||||
}
|
||||
|
||||
if err = init.generateConfig(ans); err != nil {
|
||||
init.logger.Error(err.Error())
|
||||
return ExitCodeError
|
||||
}
|
||||
|
||||
if err = init.generateTemplate(ans); err != nil {
|
||||
init.logger.Error(err.Error())
|
||||
return ExitCodeError
|
||||
}
|
||||
|
||||
success := color.CyanString("✔")
|
||||
emoji.Fprintf(os.Stdout, `
|
||||
init.logger.Log(fmt.Sprintf(`
|
||||
:sparkles: %s
|
||||
%s %s
|
||||
%s %s
|
||||
|
||||
`,
|
||||
color.GreenString("Configuration file and template generation completed!"),
|
||||
success,
|
||||
filepath.Join(answer.ConfigDir, defaultConfigFilename),
|
||||
filepath.Join(ans.ConfigDir, defaultConfigFilename),
|
||||
success,
|
||||
filepath.Join(answer.ConfigDir, defaultTemplateFilename),
|
||||
)
|
||||
filepath.Join(ans.ConfigDir, defaultTemplateFilename),
|
||||
))
|
||||
|
||||
return ExitCodeOK
|
||||
}
|
||||
|
||||
func (init *Initializer) ask() (*Answer, error) {
|
||||
answer := &Answer{}
|
||||
qs := init.createQuestions()
|
||||
err := survey.Ask(qs, answer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return answer, nil
|
||||
}
|
||||
|
||||
func (init *Initializer) createQuestions() []*survey.Question {
|
||||
originURL := init.getRepositoryURL()
|
||||
|
||||
return []*survey.Question{
|
||||
{
|
||||
Name: "repository_url",
|
||||
Prompt: &survey.Input{
|
||||
Message: "What is the URL of your repository?",
|
||||
Default: originURL,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "style",
|
||||
Prompt: &survey.Select{
|
||||
Message: "What is your favorite style?",
|
||||
Options: changelogStyles,
|
||||
Default: changelogStyles[0],
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "commit_message_format",
|
||||
Prompt: &survey.Select{
|
||||
Message: "Choose the format of your favorite commit message",
|
||||
Options: commitMessageFormats,
|
||||
Default: commitMessageFormats[0],
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "template",
|
||||
Prompt: &survey.Select{
|
||||
Message: "What is your favorite template style?",
|
||||
Options: templateStyles,
|
||||
Default: templateStyles[0],
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "include_merges",
|
||||
Prompt: &survey.Confirm{
|
||||
Message: "Do you include Merge Commit in CHANGELOG?",
|
||||
Default: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "include_reverts",
|
||||
Prompt: &survey.Confirm{
|
||||
Message: "Do you include Revert Commit in CHANGELOG?",
|
||||
Default: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "config_dir",
|
||||
Prompt: &survey.Input{
|
||||
Message: "In which directory do you output configuration files and templates?",
|
||||
Default: ".chglog",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (init *Initializer) getRepositoryURL() string {
|
||||
if init.client.CanExec() != nil || init.client.InsideWorkTree() != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
rawurl, err := init.client.Exec("config", "--get", "remote.origin.url")
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return remoteOriginURLToHTTP(rawurl)
|
||||
}
|
||||
|
||||
func (init *Initializer) generateConfigure(answer *Answer) error {
|
||||
var err error
|
||||
|
||||
err = fs.MkdirP(answer.ConfigDir)
|
||||
func (init *Initializer) generateConfig(ans *Answer) error {
|
||||
s, err := init.configBuilder.Build(ans)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config := init.createConfigYamlContent(answer)
|
||||
tpl := init.createTemplate(answer)
|
||||
return init.fs.WriteFile(filepath.Join(init.ctx.WorkingDir, ans.ConfigDir, defaultConfigFilename), []byte(s))
|
||||
}
|
||||
|
||||
configPath := filepath.Join(answer.ConfigDir, defaultConfigFilename)
|
||||
templatePath := filepath.Join(answer.ConfigDir, defaultTemplateFilename)
|
||||
|
||||
err = init.createFileWithConfirm(configPath, config)
|
||||
func (init *Initializer) generateTemplate(ans *Answer) error {
|
||||
templateBuilder := init.templateBuilderFactory(ans.Template)
|
||||
s, err := templateBuilder.Build(ans)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = init.createFileWithConfirm(templatePath, tpl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*Initializer) createFileWithConfirm(path, content string) error {
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
answer := struct {
|
||||
OK bool
|
||||
}{}
|
||||
|
||||
err := survey.Ask([]*survey.Question{
|
||||
{
|
||||
Name: "ok",
|
||||
Prompt: &survey.Confirm{
|
||||
Message: fmt.Sprintf("\"%s\" already exists. Do you want to overwrite?", path),
|
||||
Default: true,
|
||||
},
|
||||
},
|
||||
}, &answer)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !answer.OK {
|
||||
return errors.New("creation of the file was interrupted")
|
||||
}
|
||||
}
|
||||
|
||||
err := ioutil.WriteFile(path, []byte(content), os.ModePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (init *Initializer) createConfigYamlContent(answer *Answer) string {
|
||||
var (
|
||||
style = answer.Style
|
||||
template = defaultTemplateFilename
|
||||
repositoryURL = answer.RepositoryURL
|
||||
headerPattern string
|
||||
headerPatternMaps string
|
||||
)
|
||||
|
||||
switch answer.CommitMessageFormat {
|
||||
case fmtTypeScopeSubject:
|
||||
headerPattern = `^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$`
|
||||
headerPatternMaps = `
|
||||
- Type
|
||||
- Scope
|
||||
- Subject`
|
||||
case fmtTypeSubject:
|
||||
headerPattern = `^(\\w*)\\:\\s(.*)$`
|
||||
headerPatternMaps = `
|
||||
- Type
|
||||
- Subject`
|
||||
case fmtSubject:
|
||||
headerPattern = `^(.*)$`
|
||||
headerPatternMaps = `
|
||||
- Subject`
|
||||
}
|
||||
|
||||
config := fmt.Sprintf(`style: %s
|
||||
template: %s
|
||||
info:
|
||||
title: CHANGELOG
|
||||
repository_url: %s
|
||||
options:
|
||||
commits:
|
||||
# filters:
|
||||
# Type:
|
||||
# - feat
|
||||
# - fix
|
||||
# - perf
|
||||
# - refactor
|
||||
commit_groups:
|
||||
# title_maps:
|
||||
# feat: Features
|
||||
# fix: Bug Fixes
|
||||
# perf: Performance Improvements
|
||||
# refactor: Code Refactoring
|
||||
header:
|
||||
pattern: "%s"
|
||||
pattern_maps:%s
|
||||
notes:
|
||||
keywords:
|
||||
- BREAKING CHANGE`,
|
||||
style,
|
||||
template,
|
||||
repositoryURL,
|
||||
headerPattern,
|
||||
headerPatternMaps,
|
||||
)
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func (init *Initializer) createTemplate(answer *Answer) string {
|
||||
tpl := "{{range .Versions}}\n"
|
||||
|
||||
// versions
|
||||
tpl += init.versionHeader(answer.Style, answer.Template)
|
||||
|
||||
// commits
|
||||
tpl += init.commits(answer.Style, answer.Template, answer.CommitMessageFormat)
|
||||
|
||||
// merges
|
||||
if answer.IncludeReverts {
|
||||
tpl += `{{if .RevertCommits}}
|
||||
### Reverts
|
||||
{{range .RevertCommits}}
|
||||
* {{.Header}}{{end}}
|
||||
{{end}}`
|
||||
}
|
||||
|
||||
// reverts
|
||||
if answer.IncludeReverts {
|
||||
tpl += fmt.Sprintf(`{{if .MergeCommits}}
|
||||
### %s
|
||||
{{range .MergeCommits}}
|
||||
* {{.Header}}{{end}}
|
||||
{{end}}`, init.mergeTitle(answer.Style))
|
||||
}
|
||||
|
||||
tpl += `{{range .NoteGroups}}
|
||||
### {{.Title}}
|
||||
{{range .Notes}}
|
||||
{{.Body}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}`
|
||||
|
||||
return tpl
|
||||
}
|
||||
|
||||
func (*Initializer) versionHeader(style, template string) string {
|
||||
var (
|
||||
tpl string
|
||||
tagName string
|
||||
date = "{{datetime \"2006-01-02\" .Tag.Date}}"
|
||||
)
|
||||
|
||||
// parts
|
||||
switch style {
|
||||
case styleGitHub:
|
||||
tpl = "<a name=\"{{.Tag.Name}}\"></a>\n"
|
||||
tagName = "{{if .Tag.Previous}}[{{.Tag.Name}}]({{$.Info.RepositoryURL}}/compare/{{.Tag.Previous.Name}}...{{.Tag.Name}}){{else}}{{.Tag.Name}}{{end}}"
|
||||
default:
|
||||
tagName = "{{.Tag.Name}}"
|
||||
}
|
||||
|
||||
// format
|
||||
switch template {
|
||||
case tplStandard:
|
||||
tpl = fmt.Sprintf("%s## %s (%s)\n",
|
||||
tpl,
|
||||
tagName,
|
||||
date,
|
||||
)
|
||||
case tplCool:
|
||||
tpl = fmt.Sprintf("%s## %s\n\n> %s\n",
|
||||
tpl,
|
||||
tagName,
|
||||
date,
|
||||
)
|
||||
}
|
||||
|
||||
return tpl
|
||||
}
|
||||
|
||||
func (*Initializer) commits(style, template, format string) string {
|
||||
var (
|
||||
header string
|
||||
body string
|
||||
)
|
||||
|
||||
switch format {
|
||||
case fmtTypeScopeSubject, fmtTypeSubject:
|
||||
if format == fmtTypeScopeSubject {
|
||||
header = "{{if ne .Scope \"\"}}**{{.Scope}}:** {{end}}{{.Subject}}"
|
||||
} else {
|
||||
header = "{{.Subject}}"
|
||||
}
|
||||
|
||||
body = fmt.Sprintf(`### {{.Title}}
|
||||
{{range .Commits}}
|
||||
* %s{{end}}
|
||||
`, header)
|
||||
|
||||
case fmtSubject:
|
||||
body = `{{range .Commits}}
|
||||
* {{.Header}}{{end}}
|
||||
`
|
||||
}
|
||||
|
||||
return fmt.Sprintf(`{{range .CommitGroups}}
|
||||
%s{{end}}`, body)
|
||||
}
|
||||
|
||||
func (*Initializer) mergeTitle(style string) string {
|
||||
switch style {
|
||||
case styleGitHub:
|
||||
return "Pull Requests"
|
||||
default:
|
||||
return "Merges"
|
||||
}
|
||||
return init.fs.WriteFile(filepath.Join(init.ctx.WorkingDir, ans.ConfigDir, defaultTemplateFilename), []byte(s))
|
||||
}
|
||||
|
|
|
|||
76
cmd/git-chglog/initializer_test.go
Normal file
76
cmd/git-chglog/initializer_test.go
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestInitializer(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
stdout := &bytes.Buffer{}
|
||||
stderr := &bytes.Buffer{}
|
||||
|
||||
mockFs := &mockFileSystem{
|
||||
ReturnMkdirP: func(path string) error {
|
||||
if path == filepath.FromSlash("/test/config") {
|
||||
return nil
|
||||
}
|
||||
return errors.New("")
|
||||
},
|
||||
ReturnWriteFile: func(path string, content []byte) error {
|
||||
if path == filepath.FromSlash("/test/config/config.yml") || path == filepath.FromSlash("/test/config/CHANGELOG.tpl.md") {
|
||||
return nil
|
||||
}
|
||||
return errors.New("")
|
||||
},
|
||||
}
|
||||
|
||||
questioner := &mockQuestionerImpl{
|
||||
ReturnAsk: func() (*Answer, error) {
|
||||
return &Answer{
|
||||
ConfigDir: "config",
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
configBuilder := &mockConfigBuilderImpl{
|
||||
ReturnBuild: func(ans *Answer) (string, error) {
|
||||
if ans.ConfigDir == "config" {
|
||||
return "config", nil
|
||||
}
|
||||
return "", errors.New("")
|
||||
},
|
||||
}
|
||||
|
||||
tplBuilder := &mockTemplateBuilderImpl{
|
||||
ReturnBuild: func(ans *Answer) (string, error) {
|
||||
if ans.ConfigDir == "config" {
|
||||
return "template", nil
|
||||
}
|
||||
return "", errors.New("")
|
||||
},
|
||||
}
|
||||
|
||||
init := NewInitializer(
|
||||
&InitContext{
|
||||
WorkingDir: "/test",
|
||||
Stdout: stdout,
|
||||
Stderr: stderr,
|
||||
},
|
||||
mockFs,
|
||||
questioner,
|
||||
configBuilder,
|
||||
func(t string) TemplateBuilder {
|
||||
return tplBuilder
|
||||
},
|
||||
)
|
||||
|
||||
assert.Equal(ExitCodeOK, init.Run())
|
||||
assert.Equal("", stderr.String())
|
||||
assert.Contains(stdout.String(), "Configuration file and template")
|
||||
}
|
||||
181
cmd/git-chglog/kac_template_builder.go
Normal file
181
cmd/git-chglog/kac_template_builder.go
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type kacTemplateBuilderImpl struct{}
|
||||
|
||||
// NewKACTemplateBuilder ...
|
||||
func NewKACTemplateBuilder() TemplateBuilder {
|
||||
return &kacTemplateBuilderImpl{}
|
||||
}
|
||||
|
||||
// Build ...
|
||||
func (t *kacTemplateBuilderImpl) Build(ans *Answer) (string, error) {
|
||||
// unreleased
|
||||
tpl := t.unreleased(ans.Style, ans.CommitMessageFormat)
|
||||
|
||||
// version start
|
||||
tpl += "\n{{ range .Versions }}\n"
|
||||
|
||||
tpl += t.versionHeader(ans.Style)
|
||||
|
||||
// commits
|
||||
tpl += t.commits(".CommitGroups", ans.CommitMessageFormat)
|
||||
|
||||
// revert
|
||||
if ans.IncludeReverts {
|
||||
tpl += t.reverts()
|
||||
}
|
||||
|
||||
// merges
|
||||
if ans.IncludeMerges {
|
||||
tpl += t.merges(ans.Style)
|
||||
}
|
||||
|
||||
// notes
|
||||
tpl += t.notes()
|
||||
|
||||
// versions end
|
||||
tpl += "\n{{ end -}}"
|
||||
|
||||
// footer (links)
|
||||
tpl += t.footer(ans.Style)
|
||||
|
||||
return tpl, nil
|
||||
}
|
||||
|
||||
func (t *kacTemplateBuilderImpl) unreleased(style, format string) string {
|
||||
var (
|
||||
id = ""
|
||||
title = "Unreleased"
|
||||
commits = t.commits(".Unreleased.CommitGroups", format)
|
||||
)
|
||||
|
||||
switch style {
|
||||
case styleGitHub, styleGitLab, styleBitbucket:
|
||||
id = "<a name=\"unreleased\"></a>\n"
|
||||
title = fmt.Sprintf("[%s]", title)
|
||||
}
|
||||
|
||||
return fmt.Sprintf(`{{ if .Versions -}}
|
||||
%s## %s
|
||||
|
||||
{{ if .Unreleased.CommitGroups -}}
|
||||
%s{{ end -}}
|
||||
{{ end -}}
|
||||
`, id, title, commits)
|
||||
}
|
||||
|
||||
func (t *kacTemplateBuilderImpl) versionHeader(style string) string {
|
||||
var (
|
||||
id = ""
|
||||
tagName = "{{ .Tag.Name }}"
|
||||
date = "{{ datetime \"2006-01-02\" .Tag.Date }}"
|
||||
)
|
||||
|
||||
switch style {
|
||||
case styleGitHub, styleGitLab, styleBitbucket:
|
||||
id = templateTagNameAnchor
|
||||
tagName = "{{ if .Tag.Previous }}[{{ .Tag.Name }}]{{ else }}{{ .Tag.Name }}{{ end }}"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s## %s - %s\n", id, tagName, date)
|
||||
}
|
||||
|
||||
func (t *kacTemplateBuilderImpl) commits(commitGroups, format string) string {
|
||||
var (
|
||||
body string
|
||||
)
|
||||
|
||||
switch format {
|
||||
case fmtSubject.display:
|
||||
body = `{{ range .Commits -}}
|
||||
- {{ .Header }}
|
||||
{{ end }}`
|
||||
|
||||
default:
|
||||
body = `### {{ .Title }}
|
||||
{{ range .Commits -}}
|
||||
- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
|
||||
{{ end }}`
|
||||
}
|
||||
|
||||
return fmt.Sprintf(`{{ range %s -}}
|
||||
%s
|
||||
{{ end -}}
|
||||
`, commitGroups, body)
|
||||
}
|
||||
|
||||
func (t *kacTemplateBuilderImpl) reverts() string {
|
||||
return `
|
||||
{{- if .RevertCommits -}}
|
||||
### Reverts
|
||||
{{ range .RevertCommits -}}
|
||||
- {{ .Revert.Header }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
`
|
||||
}
|
||||
|
||||
func (t *kacTemplateBuilderImpl) merges(style string) string {
|
||||
var title string
|
||||
|
||||
switch style {
|
||||
case styleGitHub, styleBitbucket:
|
||||
title = "Pull Requests"
|
||||
case styleGitLab:
|
||||
title = "Merge Requests"
|
||||
default:
|
||||
title = "Merges"
|
||||
}
|
||||
|
||||
return fmt.Sprintf(`
|
||||
{{- if .MergeCommits -}}
|
||||
### %s
|
||||
{{ range .MergeCommits -}}
|
||||
- {{ .Header }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
`, title)
|
||||
}
|
||||
|
||||
func (*kacTemplateBuilderImpl) notes() string {
|
||||
return `
|
||||
{{- if .NoteGroups -}}
|
||||
{{ range .NoteGroups -}}
|
||||
### {{ .Title }}
|
||||
{{ range .Notes }}
|
||||
{{ .Body }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
{{ end -}}`
|
||||
}
|
||||
|
||||
func (*kacTemplateBuilderImpl) footer(style string) string {
|
||||
switch style {
|
||||
case styleGitHub, styleGitLab:
|
||||
return `
|
||||
|
||||
{{- if .Versions }}
|
||||
[Unreleased]: {{ .Info.RepositoryURL }}/compare/{{ $latest := index .Versions 0 }}{{ $latest.Tag.Name }}...HEAD
|
||||
{{ range .Versions -}}
|
||||
{{ if .Tag.Previous -}}
|
||||
[{{ .Tag.Name }}]: {{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}`
|
||||
case styleBitbucket:
|
||||
return `
|
||||
|
||||
{{- if .Versions }}
|
||||
[Unreleased]: {{ .Info.RepositoryURL }}/compare/HEAD..{{ $latest := index .Versions 0 }}{{ $latest.Tag.Name }}
|
||||
{{ range .Versions -}}
|
||||
{{ if .Tag.Previous -}}
|
||||
[{{ .Tag.Name }}]: {{ $.Info.RepositoryURL }}/compare/{{ .Tag.Name }}..{{ .Tag.Previous.Name }}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}`
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
196
cmd/git-chglog/kac_template_builder_test.go
Normal file
196
cmd/git-chglog/kac_template_builder_test.go
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestKACTemplateBuilderDefault(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
builder := NewKACTemplateBuilder()
|
||||
|
||||
out, err := builder.Build(&Answer{
|
||||
Style: styleGitHub,
|
||||
CommitMessageFormat: fmtTypeScopeSubject.display,
|
||||
Template: tplKeepAChangelog.display,
|
||||
IncludeMerges: true,
|
||||
IncludeReverts: true,
|
||||
})
|
||||
|
||||
assert.Nil(err)
|
||||
assert.Equal(`{{ if .Versions -}}
|
||||
<a name="unreleased"></a>
|
||||
## [Unreleased]
|
||||
|
||||
{{ if .Unreleased.CommitGroups -}}
|
||||
{{ range .Unreleased.CommitGroups -}}
|
||||
### {{ .Title }}
|
||||
{{ range .Commits -}}
|
||||
- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
|
||||
{{ range .Versions }}
|
||||
<a name="{{ .Tag.Name }}"></a>
|
||||
## {{ if .Tag.Previous }}[{{ .Tag.Name }}]{{ else }}{{ .Tag.Name }}{{ end }} - {{ datetime "2006-01-02" .Tag.Date }}
|
||||
{{ range .CommitGroups -}}
|
||||
### {{ .Title }}
|
||||
{{ range .Commits -}}
|
||||
- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .RevertCommits -}}
|
||||
### Reverts
|
||||
{{ range .RevertCommits -}}
|
||||
- {{ .Revert.Header }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .MergeCommits -}}
|
||||
### Pull Requests
|
||||
{{ range .MergeCommits -}}
|
||||
- {{ .Header }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .NoteGroups -}}
|
||||
{{ range .NoteGroups -}}
|
||||
### {{ .Title }}
|
||||
{{ range .Notes }}
|
||||
{{ .Body }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .Versions }}
|
||||
[Unreleased]: {{ .Info.RepositoryURL }}/compare/{{ $latest := index .Versions 0 }}{{ $latest.Tag.Name }}...HEAD
|
||||
{{ range .Versions -}}
|
||||
{{ if .Tag.Previous -}}
|
||||
[{{ .Tag.Name }}]: {{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}`, out)
|
||||
}
|
||||
|
||||
func TestKACTemplateBuilderNone(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
builder := NewKACTemplateBuilder()
|
||||
|
||||
out, err := builder.Build(&Answer{
|
||||
Style: styleNone,
|
||||
CommitMessageFormat: fmtTypeScopeSubject.display,
|
||||
Template: tplKeepAChangelog.display,
|
||||
IncludeMerges: true,
|
||||
IncludeReverts: true,
|
||||
})
|
||||
|
||||
assert.Nil(err)
|
||||
assert.Equal(`{{ if .Versions -}}
|
||||
## Unreleased
|
||||
|
||||
{{ if .Unreleased.CommitGroups -}}
|
||||
{{ range .Unreleased.CommitGroups -}}
|
||||
### {{ .Title }}
|
||||
{{ range .Commits -}}
|
||||
- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
|
||||
{{ range .Versions }}
|
||||
## {{ .Tag.Name }} - {{ datetime "2006-01-02" .Tag.Date }}
|
||||
{{ range .CommitGroups -}}
|
||||
### {{ .Title }}
|
||||
{{ range .Commits -}}
|
||||
- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .RevertCommits -}}
|
||||
### Reverts
|
||||
{{ range .RevertCommits -}}
|
||||
- {{ .Revert.Header }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .MergeCommits -}}
|
||||
### Merges
|
||||
{{ range .MergeCommits -}}
|
||||
- {{ .Header }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .NoteGroups -}}
|
||||
{{ range .NoteGroups -}}
|
||||
### {{ .Title }}
|
||||
{{ range .Notes }}
|
||||
{{ .Body }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}`, out)
|
||||
}
|
||||
|
||||
func TestKACTemplateBuilderSubject(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
builder := NewKACTemplateBuilder()
|
||||
|
||||
out, err := builder.Build(&Answer{
|
||||
Style: styleNone,
|
||||
CommitMessageFormat: fmtSubject.display,
|
||||
Template: tplKeepAChangelog.display,
|
||||
IncludeMerges: true,
|
||||
IncludeReverts: true,
|
||||
})
|
||||
|
||||
assert.Nil(err)
|
||||
assert.Equal(`{{ if .Versions -}}
|
||||
## Unreleased
|
||||
|
||||
{{ if .Unreleased.CommitGroups -}}
|
||||
{{ range .Unreleased.CommitGroups -}}
|
||||
{{ range .Commits -}}
|
||||
- {{ .Header }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
|
||||
{{ range .Versions }}
|
||||
## {{ .Tag.Name }} - {{ datetime "2006-01-02" .Tag.Date }}
|
||||
{{ range .CommitGroups -}}
|
||||
{{ range .Commits -}}
|
||||
- {{ .Header }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .RevertCommits -}}
|
||||
### Reverts
|
||||
{{ range .RevertCommits -}}
|
||||
- {{ .Revert.Header }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .MergeCommits -}}
|
||||
### Merges
|
||||
{{ range .MergeCommits -}}
|
||||
- {{ .Header }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .NoteGroups -}}
|
||||
{{ range .NoteGroups -}}
|
||||
### {{ .Title }}
|
||||
{{ range .Notes }}
|
||||
{{ .Body }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}`, out)
|
||||
}
|
||||
|
|
@ -2,13 +2,22 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/mattn/go-colorable"
|
||||
"github.com/tsuyoshiwada/go-gitcmd"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// version is passed in via LDFLAGS main.version
|
||||
var version string
|
||||
|
||||
// CreateApp creates and initializes CLI application
|
||||
// with description, flags, version, etc.
|
||||
func CreateApp(actionFunc cli.ActionFunc) *cli.App {
|
||||
ttl := color.New(color.FgYellow).SprintFunc()
|
||||
|
||||
cli.AppHelpTemplate = fmt.Sprintf(`
|
||||
|
|
@ -50,56 +59,130 @@ func main() {
|
|||
|
||||
$ {{.Name}} --config custom/dir/config.yml
|
||||
|
||||
The adove is a command that uses a configuration file placed other than ".chglog/config.yml".
|
||||
The above is a command that uses a configuration file placed other than ".chglog/config.yml".
|
||||
|
||||
$ {{.Name}} --path path/to/my/component --output CHANGELOG.component.md
|
||||
|
||||
Filter commits by specific paths or files in git and output to a component specific changelog.
|
||||
`,
|
||||
ttl("USAGE:"),
|
||||
ttl("OPTIONS:"),
|
||||
ttl("EXAMPLE:"),
|
||||
)
|
||||
|
||||
cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) {
|
||||
cli.HelpPrinterCustom(colorable.NewColorableStdout(), templ, data, nil)
|
||||
}
|
||||
|
||||
app := cli.NewApp()
|
||||
app.Name = "git-chglog"
|
||||
app.Usage = "todo usage for git-chglog"
|
||||
app.Version = Version
|
||||
app.Version = version
|
||||
|
||||
app.Flags = []cli.Flag{
|
||||
// init
|
||||
cli.BoolFlag{
|
||||
&cli.BoolFlag{
|
||||
Name: "init",
|
||||
Usage: "generate the git-chglog configuration file in interactive",
|
||||
},
|
||||
|
||||
// path
|
||||
&cli.StringSliceFlag{
|
||||
Name: "path",
|
||||
Usage: "Filter commits by path(s). Can use multiple times.",
|
||||
},
|
||||
|
||||
// config
|
||||
cli.StringFlag{
|
||||
Name: "config, c",
|
||||
Usage: "specifies a different configuration file to pick up",
|
||||
Value: ".chglog/config.yml",
|
||||
&cli.StringFlag{
|
||||
Name: "config, c",
|
||||
Aliases: []string{"c"},
|
||||
Usage: "specifies a different configuration file to pick up",
|
||||
Value: ".chglog/config.yml",
|
||||
},
|
||||
|
||||
// template
|
||||
&cli.StringFlag{
|
||||
Name: "template",
|
||||
Aliases: []string{"t"},
|
||||
Usage: "specifies a template file to pick up. If not specified, use the one in config",
|
||||
},
|
||||
|
||||
// repository url
|
||||
&cli.StringFlag{
|
||||
Name: "repository-url",
|
||||
Usage: "specifies git repo URL. If not specified, use 'repository_url' in config",
|
||||
},
|
||||
|
||||
// output
|
||||
cli.StringFlag{
|
||||
Name: "output, o",
|
||||
Usage: "output path and filename for the changelogs. If not specified, output to stdout",
|
||||
&cli.StringFlag{
|
||||
Name: "output",
|
||||
Aliases: []string{"o"},
|
||||
Usage: "output path and filename for the changelogs. If not specified, output to stdout",
|
||||
},
|
||||
|
||||
&cli.StringFlag{
|
||||
Name: "next-tag",
|
||||
Usage: "treat unreleased commits as specified tags (EXPERIMENTAL)",
|
||||
},
|
||||
|
||||
// silent
|
||||
cli.BoolFlag{
|
||||
&cli.BoolFlag{
|
||||
Name: "silent",
|
||||
Usage: "disable stdout output",
|
||||
},
|
||||
|
||||
// no-color
|
||||
cli.BoolFlag{
|
||||
Name: "no-color",
|
||||
Usage: "disable color output",
|
||||
EnvVar: "NO_COLOR",
|
||||
&cli.BoolFlag{
|
||||
Name: "no-color",
|
||||
Usage: "disable color output",
|
||||
EnvVars: []string{"NO_COLOR"},
|
||||
},
|
||||
|
||||
// no-emoji
|
||||
cli.BoolFlag{
|
||||
Name: "no-emoji",
|
||||
Usage: "disable emoji output",
|
||||
EnvVar: "NO_EMOJI",
|
||||
&cli.BoolFlag{
|
||||
Name: "no-emoji",
|
||||
Usage: "disable emoji output",
|
||||
EnvVars: []string{"NO_EMOJI"},
|
||||
},
|
||||
|
||||
// no-case
|
||||
&cli.BoolFlag{
|
||||
Name: "no-case",
|
||||
Usage: "disable case sensitive filters",
|
||||
},
|
||||
|
||||
// tag-filter-pattern
|
||||
&cli.StringFlag{
|
||||
Name: "tag-filter-pattern",
|
||||
Usage: "Regular expression of tag filter. Is specified, only matched tags will be picked",
|
||||
},
|
||||
|
||||
// jira-url
|
||||
&cli.StringFlag{
|
||||
Name: "jira-url",
|
||||
Usage: "Jira URL",
|
||||
EnvVars: []string{"JIRA_URL"},
|
||||
},
|
||||
|
||||
// jira-username
|
||||
&cli.StringFlag{
|
||||
Name: "jira-username",
|
||||
Usage: "Jira username",
|
||||
EnvVars: []string{"JIRA_USERNAME"},
|
||||
},
|
||||
|
||||
// jira-token
|
||||
&cli.StringFlag{
|
||||
Name: "jira-token",
|
||||
Usage: "Jira token",
|
||||
EnvVars: []string{"JIRA_TOKEN"},
|
||||
},
|
||||
|
||||
// sort
|
||||
&cli.StringFlag{
|
||||
Name: "sort",
|
||||
Usage: "Specify how to sort tags; currently supports \"date\" or by \"semver\"",
|
||||
DefaultText: "date",
|
||||
},
|
||||
|
||||
// help & version
|
||||
|
|
@ -107,42 +190,79 @@ func main() {
|
|||
cli.VersionFlag,
|
||||
}
|
||||
|
||||
app.Action = func(c *cli.Context) error {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "failed to get working directory", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
app.Action = actionFunc
|
||||
|
||||
// initializer
|
||||
if c.Bool("init") {
|
||||
os.Exit(NewInitializer().Run())
|
||||
}
|
||||
return app
|
||||
}
|
||||
|
||||
// chglog
|
||||
ctx := &Context{
|
||||
WorkingDir: wd,
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
ConfigPath: c.String("config"),
|
||||
OutputPath: c.String("output"),
|
||||
Silent: c.Bool("silent"),
|
||||
NoColor: c.Bool("no-color"),
|
||||
NoEmoji: c.Bool("no-emoji"),
|
||||
Query: c.Args().First(),
|
||||
}
|
||||
|
||||
chglogCLI := NewCLI(
|
||||
ctx,
|
||||
fs,
|
||||
NewConfigLoader(),
|
||||
NewGenerator(),
|
||||
)
|
||||
|
||||
os.Exit(chglogCLI.Run())
|
||||
|
||||
return nil
|
||||
// AppAction is a callback function to create initializer
|
||||
// and CLIContext and ultimately run the application.
|
||||
func AppAction(c *cli.Context) error {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "failed to get working directory", err)
|
||||
os.Exit(ExitCodeError)
|
||||
}
|
||||
|
||||
app.Run(os.Args)
|
||||
// initializer
|
||||
if c.Bool("init") {
|
||||
initializer := NewInitializer(
|
||||
&InitContext{
|
||||
WorkingDir: wd,
|
||||
Stdout: colorable.NewColorableStdout(),
|
||||
Stderr: colorable.NewColorableStderr(),
|
||||
},
|
||||
fs,
|
||||
NewQuestioner(
|
||||
gitcmd.New(&gitcmd.Config{
|
||||
Bin: "git",
|
||||
}),
|
||||
fs,
|
||||
),
|
||||
NewConfigBuilder(),
|
||||
templateBuilderFactory,
|
||||
)
|
||||
|
||||
os.Exit(initializer.Run())
|
||||
}
|
||||
|
||||
// chglog
|
||||
chglogCLI := NewCLI(
|
||||
&CLIContext{
|
||||
WorkingDir: wd,
|
||||
Stdout: colorable.NewColorableStdout(),
|
||||
Stderr: colorable.NewColorableStderr(),
|
||||
ConfigPath: c.String("config"),
|
||||
Template: c.String("template"),
|
||||
RepositoryURL: c.String("repository-url"),
|
||||
OutputPath: c.String("output"),
|
||||
Silent: c.Bool("silent"),
|
||||
NoColor: c.Bool("no-color"),
|
||||
NoEmoji: c.Bool("no-emoji"),
|
||||
NoCaseSensitive: c.Bool("no-case"),
|
||||
Query: c.Args().First(),
|
||||
NextTag: c.String("next-tag"),
|
||||
TagFilterPattern: c.String("tag-filter-pattern"),
|
||||
JiraUsername: c.String("jira-username"),
|
||||
JiraToken: c.String("jira-token"),
|
||||
JiraURL: c.String("jira-url"),
|
||||
Paths: c.StringSlice("path"),
|
||||
Sort: c.String("sort"),
|
||||
},
|
||||
fs,
|
||||
NewConfigLoader(),
|
||||
NewGenerator(),
|
||||
)
|
||||
|
||||
os.Exit(chglogCLI.Run())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := CreateApp(AppAction)
|
||||
err := app.Run(os.Args)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
45
cmd/git-chglog/main_test.go
Normal file
45
cmd/git-chglog/main_test.go
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var gAssert *assert.Assertions
|
||||
|
||||
func mockAppAction(c *cli.Context) error {
|
||||
assert := gAssert
|
||||
assert.Equal("c.yml", c.String("config"))
|
||||
assert.Equal("^v", c.String("tag-filter-pattern"))
|
||||
assert.Equal("o.md", c.String("output"))
|
||||
assert.Equal("v5", c.String("next-tag"))
|
||||
assert.True(c.Bool("silent"))
|
||||
assert.True(c.Bool("no-color"))
|
||||
assert.True(c.Bool("no-emoji"))
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestCreateApp(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
assert.True(true)
|
||||
gAssert = assert
|
||||
|
||||
app := CreateApp(mockAppAction)
|
||||
args := []string{
|
||||
"git-chglog",
|
||||
"--silent",
|
||||
"--no-color",
|
||||
"--no-emoji",
|
||||
"--config", "c.yml",
|
||||
"--output", "o.md",
|
||||
"--next-tag", "v5",
|
||||
"--tag-filter-pattern", "^v",
|
||||
}
|
||||
err := app.Run(args)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
|
@ -16,7 +16,9 @@ type ProcessorFactory struct {
|
|||
func NewProcessorFactory() *ProcessorFactory {
|
||||
return &ProcessorFactory{
|
||||
hostRegistry: map[string]string{
|
||||
"github": "github.com",
|
||||
"github": "github.com",
|
||||
"gitlab": "gitlab.com",
|
||||
"bitbucket": "bitbucket.org",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -41,6 +43,14 @@ func (factory *ProcessorFactory) Create(config *Config) (chglog.Processor, error
|
|||
return &chglog.GitHubProcessor{
|
||||
Host: fmt.Sprintf("%s://%s", obj.Scheme, obj.Host),
|
||||
}, nil
|
||||
case "gitlab.com":
|
||||
return &chglog.GitLabProcessor{
|
||||
Host: fmt.Sprintf("%s://%s", obj.Scheme, obj.Host),
|
||||
}, nil
|
||||
case "bitbucket.org":
|
||||
return &chglog.BitbucketProcessor{
|
||||
Host: fmt.Sprintf("%s://%s", obj.Scheme, obj.Host),
|
||||
}, nil
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@ package main
|
|||
import (
|
||||
"testing"
|
||||
|
||||
chglog "github.com/git-chglog/git-chglog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
chglog "github.com/git-chglog/git-chglog"
|
||||
)
|
||||
|
||||
func TestProcessorFactory(t *testing.T) {
|
||||
|
|
@ -56,3 +57,75 @@ func TestProcessorFactoryForGitHub(t *testing.T) {
|
|||
processor,
|
||||
)
|
||||
}
|
||||
|
||||
func TestProcessorFactoryForGitLab(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
factory := NewProcessorFactory()
|
||||
|
||||
// gitlab.com
|
||||
processor, err := factory.Create(&Config{
|
||||
Info: Info{
|
||||
RepositoryURL: "https://gitlab.com/owner/repo",
|
||||
},
|
||||
})
|
||||
|
||||
assert.Nil(err)
|
||||
assert.Equal(
|
||||
&chglog.GitLabProcessor{
|
||||
Host: "https://gitlab.com",
|
||||
},
|
||||
processor,
|
||||
)
|
||||
|
||||
// self-hosted
|
||||
processor, err = factory.Create(&Config{
|
||||
Style: "gitlab",
|
||||
Info: Info{
|
||||
RepositoryURL: "https://original-gitserver.com/owner/repo",
|
||||
},
|
||||
})
|
||||
|
||||
assert.Nil(err)
|
||||
assert.Equal(
|
||||
&chglog.GitLabProcessor{
|
||||
Host: "https://original-gitserver.com",
|
||||
},
|
||||
processor,
|
||||
)
|
||||
}
|
||||
|
||||
func TestProcessorFactoryForBitbucket(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
factory := NewProcessorFactory()
|
||||
|
||||
// bitbucket.org
|
||||
processor, err := factory.Create(&Config{
|
||||
Info: Info{
|
||||
RepositoryURL: "https://bitbucket.org/owner/repo",
|
||||
},
|
||||
})
|
||||
|
||||
assert.Nil(err)
|
||||
assert.Equal(
|
||||
&chglog.BitbucketProcessor{
|
||||
Host: "https://bitbucket.org",
|
||||
},
|
||||
processor,
|
||||
)
|
||||
|
||||
// self-hosted
|
||||
processor, err = factory.Create(&Config{
|
||||
Style: "bitbucket",
|
||||
Info: Info{
|
||||
RepositoryURL: "https://original-gitserver.com/owner/repo",
|
||||
},
|
||||
})
|
||||
|
||||
assert.Nil(err)
|
||||
assert.Equal(
|
||||
&chglog.BitbucketProcessor{
|
||||
Host: "https://original-gitserver.com",
|
||||
},
|
||||
processor,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
196
cmd/git-chglog/questioner.go
Normal file
196
cmd/git-chglog/questioner.go
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/tsuyoshiwada/go-gitcmd"
|
||||
)
|
||||
|
||||
// Answer ...
|
||||
type Answer struct {
|
||||
RepositoryURL string `survey:"repository_url"`
|
||||
Style string `survey:"style"`
|
||||
CommitMessageFormat string `survey:"commit_message_format"`
|
||||
Template string `survey:"template"`
|
||||
IncludeMerges bool `survey:"include_merges"`
|
||||
IncludeReverts bool `survey:"include_reverts"`
|
||||
ConfigDir string `survey:"config_dir"`
|
||||
}
|
||||
|
||||
// Questioner ...
|
||||
type Questioner interface {
|
||||
Ask() (*Answer, error)
|
||||
}
|
||||
|
||||
type questionerImpl struct {
|
||||
client gitcmd.Client
|
||||
fs FileSystem
|
||||
}
|
||||
|
||||
// NewQuestioner ...
|
||||
func NewQuestioner(client gitcmd.Client, fs FileSystem) Questioner {
|
||||
return &questionerImpl{
|
||||
client: client,
|
||||
fs: fs,
|
||||
}
|
||||
}
|
||||
|
||||
// Ask ...
|
||||
func (q *questionerImpl) Ask() (*Answer, error) {
|
||||
ans, err := q.ask()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config := filepath.Join(ans.ConfigDir, defaultConfigFilename)
|
||||
tpl := filepath.Join(ans.ConfigDir, defaultTemplateFilename)
|
||||
c := q.fs.Exists(config)
|
||||
t := q.fs.Exists(tpl)
|
||||
msg := ""
|
||||
|
||||
switch {
|
||||
case c && t:
|
||||
msg = fmt.Sprintf("\"%s\" and \"%s\" already exists. Do you want to overwrite?", config, tpl)
|
||||
case c:
|
||||
msg = fmt.Sprintf("\"%s\" already exists. Do you want to overwrite?", config)
|
||||
case t:
|
||||
msg = fmt.Sprintf("\"%s\" already exists. Do you want to overwrite?", tpl)
|
||||
}
|
||||
|
||||
if msg != "" {
|
||||
overwrite := false
|
||||
err = survey.AskOne(&survey.Confirm{
|
||||
Message: msg,
|
||||
Default: true,
|
||||
}, &overwrite, nil)
|
||||
|
||||
if err != nil || !overwrite {
|
||||
return nil, errors.New("creation of the file was interrupted")
|
||||
}
|
||||
}
|
||||
|
||||
return ans, nil
|
||||
}
|
||||
|
||||
func (q *questionerImpl) ask() (*Answer, error) {
|
||||
ans := &Answer{}
|
||||
fmts := q.getPreviewableList(formats)
|
||||
tpls := q.getPreviewableList(templates)
|
||||
|
||||
var previewableTransform = func(ans interface{}) (newAns interface{}) {
|
||||
if s, ok := ans.(survey.OptionAnswer); ok {
|
||||
newAns = survey.OptionAnswer{
|
||||
Value: q.parsePreviewableList(s.Value),
|
||||
Index: s.Index,
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
questions := []*survey.Question{
|
||||
{
|
||||
Name: "repository_url",
|
||||
Prompt: &survey.Input{
|
||||
Message: "What is the URL of your repository?",
|
||||
Default: q.getRepositoryURL(),
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "style",
|
||||
Prompt: &survey.Select{
|
||||
Message: "What is your favorite style?",
|
||||
Options: styles,
|
||||
Default: styles[0],
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "commit_message_format",
|
||||
Prompt: &survey.Select{
|
||||
Message: "Choose the format of your favorite commit message",
|
||||
Options: fmts,
|
||||
Default: fmts[0],
|
||||
},
|
||||
Transform: previewableTransform,
|
||||
},
|
||||
{
|
||||
Name: "template",
|
||||
Prompt: &survey.Select{
|
||||
Message: "What is your favorite template style?",
|
||||
Options: tpls,
|
||||
Default: tpls[0],
|
||||
},
|
||||
Transform: previewableTransform,
|
||||
},
|
||||
{
|
||||
Name: "include_merges",
|
||||
Prompt: &survey.Confirm{
|
||||
Message: "Do you include Merge Commit in CHANGELOG?",
|
||||
Default: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "include_reverts",
|
||||
Prompt: &survey.Confirm{
|
||||
Message: "Do you include Revert Commit in CHANGELOG?",
|
||||
Default: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "config_dir",
|
||||
Prompt: &survey.Input{
|
||||
Message: "In which directory do you output configuration files and templates?",
|
||||
Default: defaultConfigDir,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := survey.Ask(questions, ans)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ans, nil
|
||||
}
|
||||
|
||||
func (*questionerImpl) getPreviewableList(list []Previewable) []string {
|
||||
arr := make([]string, len(list))
|
||||
max := 0
|
||||
|
||||
for _, p := range list {
|
||||
l := len(p.Display())
|
||||
if max < l {
|
||||
max = l
|
||||
}
|
||||
}
|
||||
|
||||
for i, p := range list {
|
||||
arr[i] = fmt.Sprintf(
|
||||
"%s -- %s",
|
||||
p.Display()+strings.Repeat(" ", max-len(p.Display())),
|
||||
p.Preview(),
|
||||
)
|
||||
}
|
||||
|
||||
return arr
|
||||
}
|
||||
|
||||
func (*questionerImpl) parsePreviewableList(input string) string {
|
||||
return strings.TrimSpace(strings.Split(input, "--")[0])
|
||||
}
|
||||
|
||||
func (q *questionerImpl) getRepositoryURL() string {
|
||||
if q.client.CanExec() != nil || q.client.InsideWorkTree() != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
rawurl, err := q.client.Exec("config", "--get", "remote.origin.url")
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return remoteOriginURLToHTTP(rawurl)
|
||||
}
|
||||
9
cmd/git-chglog/questioner_mock.go
Normal file
9
cmd/git-chglog/questioner_mock.go
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
package main
|
||||
|
||||
type mockQuestionerImpl struct {
|
||||
ReturnAsk func() (*Answer, error)
|
||||
}
|
||||
|
||||
func (m *mockQuestionerImpl) Ask() (*Answer, error) {
|
||||
return m.ReturnAsk()
|
||||
}
|
||||
20
cmd/git-chglog/template_builder.go
Normal file
20
cmd/git-chglog/template_builder.go
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
package main
|
||||
|
||||
const templateTagNameAnchor = "<a name=\"{{ .Tag.Name }}\"></a>\n"
|
||||
|
||||
// TemplateBuilder ...
|
||||
type TemplateBuilder interface {
|
||||
Builder
|
||||
}
|
||||
|
||||
// TemplateBuilderFactory ...
|
||||
type TemplateBuilderFactory = func(string) TemplateBuilder
|
||||
|
||||
func templateBuilderFactory(template string) TemplateBuilder {
|
||||
switch template {
|
||||
case tplKeepAChangelog.display:
|
||||
return NewKACTemplateBuilder()
|
||||
default:
|
||||
return NewCustomTemplateBuilder()
|
||||
}
|
||||
}
|
||||
9
cmd/git-chglog/template_builder_mock.go
Normal file
9
cmd/git-chglog/template_builder_mock.go
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
package main
|
||||
|
||||
type mockTemplateBuilderImpl struct {
|
||||
ReturnBuild func(*Answer) (string, error)
|
||||
}
|
||||
|
||||
func (m *mockTemplateBuilderImpl) Build(ans *Answer) (string, error) {
|
||||
return m.ReturnBuild(ans)
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
var reSSH = regexp.MustCompile("^\\w+@([\\w\\.\\-]+):([\\w\\.\\-]+)\\/([\\w\\.\\-]+)$")
|
||||
var reSSH = regexp.MustCompile(`^\w+@([\w\.\-]+):([\w\.\-]+)\/([\w\.\-]+)$`)
|
||||
|
||||
func remoteOriginURLToHTTP(rawurl string) string {
|
||||
if rawurl == "" {
|
||||
|
|
|
|||
198
cmd/git-chglog/variables.go
Normal file
198
cmd/git-chglog/variables.go
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Previewable ...
|
||||
type Previewable interface {
|
||||
Display() string
|
||||
Preview() string
|
||||
}
|
||||
|
||||
// Defaults
|
||||
var (
|
||||
defaultConfigDir = ".chglog"
|
||||
defaultConfigFilename = "config.yml"
|
||||
defaultTemplateFilename = "CHANGELOG.tpl.md"
|
||||
)
|
||||
|
||||
// Styles
|
||||
var (
|
||||
styleGitHub = "github"
|
||||
styleGitLab = "gitlab"
|
||||
styleBitbucket = "bitbucket"
|
||||
styleNone = "none"
|
||||
styles = []string{
|
||||
styleGitHub,
|
||||
styleGitLab,
|
||||
styleBitbucket,
|
||||
styleNone,
|
||||
}
|
||||
)
|
||||
|
||||
type typeSample struct {
|
||||
typeName string
|
||||
title string
|
||||
}
|
||||
|
||||
// CommitMessageFormat ...
|
||||
type CommitMessageFormat struct {
|
||||
display string
|
||||
preview string
|
||||
pattern string
|
||||
patternMaps []string
|
||||
typeSamples []typeSample
|
||||
}
|
||||
|
||||
// Display ...
|
||||
func (f *CommitMessageFormat) Display() string {
|
||||
return f.display
|
||||
}
|
||||
|
||||
// Preview ...
|
||||
func (f *CommitMessageFormat) Preview() string {
|
||||
return f.preview
|
||||
}
|
||||
|
||||
// PatternMapString ...
|
||||
func (f *CommitMessageFormat) PatternMapString() string {
|
||||
if len(f.patternMaps) == 0 {
|
||||
return " []"
|
||||
}
|
||||
|
||||
arr := make([]string, len(f.patternMaps))
|
||||
for i, p := range f.patternMaps {
|
||||
arr[i] = fmt.Sprintf(
|
||||
"%s- %s",
|
||||
strings.Repeat(" ", 6),
|
||||
p,
|
||||
)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("\n%s", strings.Join(arr, "\n"))
|
||||
}
|
||||
|
||||
// FilterTypesString ...
|
||||
func (f *CommitMessageFormat) FilterTypesString() string {
|
||||
if len(f.typeSamples) == 0 {
|
||||
return " []"
|
||||
}
|
||||
|
||||
arr := make([]string, len(f.typeSamples))
|
||||
for i, t := range f.typeSamples {
|
||||
arr[i] = fmt.Sprintf(
|
||||
"%s#%s- %s",
|
||||
strings.Repeat(" ", 4), strings.Repeat(" ", 5),
|
||||
t.typeName)
|
||||
}
|
||||
return fmt.Sprintf("\n%s", strings.Join(arr, "\n"))
|
||||
}
|
||||
|
||||
// TitleMapsString ...
|
||||
func (f *CommitMessageFormat) TitleMapsString() string {
|
||||
if len(f.typeSamples) == 0 {
|
||||
return " []"
|
||||
}
|
||||
|
||||
arr := make([]string, len(f.typeSamples))
|
||||
for i, t := range f.typeSamples {
|
||||
arr[i] = fmt.Sprintf(
|
||||
"%s#%s%s: %s",
|
||||
strings.Repeat(" ", 4), strings.Repeat(" ", 3),
|
||||
t.typeName, t.title)
|
||||
}
|
||||
return fmt.Sprintf("\n%s", strings.Join(arr, "\n"))
|
||||
}
|
||||
|
||||
// Formats
|
||||
var (
|
||||
fmtTypeScopeSubject = &CommitMessageFormat{
|
||||
display: "<type>(<scope>): <subject>",
|
||||
preview: "feat(core): Add new feature",
|
||||
pattern: `^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$`,
|
||||
patternMaps: []string{"Type", "Scope", "Subject"},
|
||||
typeSamples: []typeSample{
|
||||
{"feat", "Features"}, {"fix", "Bug Fixes"},
|
||||
{"perf", "Performance Improvements"}, {"refactor", "Code Refactoring"}},
|
||||
}
|
||||
fmtTypeSubject = &CommitMessageFormat{
|
||||
display: "<type>: <subject>",
|
||||
preview: "feat: Add new feature",
|
||||
pattern: `^(\\w*)\\:\\s(.*)$`,
|
||||
patternMaps: []string{"Type", "Subject"},
|
||||
typeSamples: []typeSample{
|
||||
{"feat", "Features"}, {"fix", "Bug Fixes"},
|
||||
{"perf", "Performance Improvements"}, {"refactor", "Code Refactoring"}},
|
||||
}
|
||||
fmtGitBasic = &CommitMessageFormat{
|
||||
display: "<<type> subject>",
|
||||
preview: "Add new feature",
|
||||
pattern: `^((\\w+)\\s.*)$`,
|
||||
patternMaps: []string{"Subject", "Type"},
|
||||
typeSamples: []typeSample{
|
||||
{"feat", "Features"}, {"fix", "Bug Fixes"},
|
||||
{"perf", "Performance Improvements"}, {"refactor", "Code Refactoring"}},
|
||||
}
|
||||
fmtSubject = &CommitMessageFormat{
|
||||
display: "<subject>",
|
||||
preview: "Add new feature (Not detect `type` field)",
|
||||
pattern: `^(.*)$`,
|
||||
patternMaps: []string{"Subject"},
|
||||
typeSamples: []typeSample{},
|
||||
}
|
||||
fmtCommitEmoji = &CommitMessageFormat{
|
||||
display: ":<type>: <subject>",
|
||||
preview: ":sparkles: Add new feature (Commit message with emoji format)",
|
||||
pattern: `^:(\\w*)\\:\\s(.*)$`,
|
||||
patternMaps: []string{"Type", "Subject"},
|
||||
typeSamples: []typeSample{
|
||||
{"sparkles", "Features"}, {"bug", "Bug Fixes"},
|
||||
{"zap", "Performance Improvements"}, {"recycle", "Code Refactoring"}},
|
||||
}
|
||||
formats = []Previewable{
|
||||
fmtTypeScopeSubject,
|
||||
fmtTypeSubject,
|
||||
fmtGitBasic,
|
||||
fmtSubject,
|
||||
fmtCommitEmoji,
|
||||
}
|
||||
)
|
||||
|
||||
// TemplateStyleFormat ...
|
||||
type TemplateStyleFormat struct {
|
||||
preview string
|
||||
display string
|
||||
}
|
||||
|
||||
// Display ...
|
||||
func (t *TemplateStyleFormat) Display() string {
|
||||
return t.display
|
||||
}
|
||||
|
||||
// Preview ...
|
||||
func (t *TemplateStyleFormat) Preview() string {
|
||||
return t.preview
|
||||
}
|
||||
|
||||
// Templates
|
||||
var (
|
||||
tplKeepAChangelog = &TemplateStyleFormat{
|
||||
display: "keep-a-changelog",
|
||||
preview: "https://github.com/git-chglog/example-type-scope-subject/blob/master/CHANGELOG.kac.md",
|
||||
}
|
||||
tplStandard = &TemplateStyleFormat{
|
||||
display: "standard",
|
||||
preview: "https://github.com/git-chglog/example-type-scope-subject/blob/master/CHANGELOG.standard.md",
|
||||
}
|
||||
tplCool = &TemplateStyleFormat{
|
||||
display: "cool",
|
||||
preview: "https://github.com/git-chglog/example-type-scope-subject/blob/master/CHANGELOG.cool.md",
|
||||
}
|
||||
templates = []Previewable{
|
||||
tplKeepAChangelog,
|
||||
tplStandard,
|
||||
tplCool,
|
||||
}
|
||||
)
|
||||
76
cmd/git-chglog/variables_test.go
Normal file
76
cmd/git-chglog/variables_test.go
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCommitMessageFormatPatternMaps(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
f := &CommitMessageFormat{
|
||||
patternMaps: []string{
|
||||
"Type",
|
||||
"Scope",
|
||||
"Subject",
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(`
|
||||
- Type
|
||||
- Scope
|
||||
- Subject`, f.PatternMapString())
|
||||
|
||||
f = &CommitMessageFormat{
|
||||
patternMaps: []string{},
|
||||
}
|
||||
|
||||
assert.Equal(" []", f.PatternMapString())
|
||||
}
|
||||
|
||||
func TestCommitMessageFormatFilterTypes(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
f := &CommitMessageFormat{
|
||||
typeSamples: []typeSample{
|
||||
{"feat", "Features"}, {"fix", "Bug Fixes"},
|
||||
{"perf", "Performance Improvements"}, {"refactor", "Code Refactoring"},
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(`
|
||||
# - feat
|
||||
# - fix
|
||||
# - perf
|
||||
# - refactor`, f.FilterTypesString())
|
||||
|
||||
f = &CommitMessageFormat{
|
||||
patternMaps: []string{},
|
||||
}
|
||||
|
||||
assert.Equal(" []", f.FilterTypesString())
|
||||
}
|
||||
|
||||
func TestCommitMessageFormatTitleMaps(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
f := &CommitMessageFormat{
|
||||
typeSamples: []typeSample{
|
||||
{"feat", "Features"}, {"fix", "Bug Fixes"},
|
||||
{"perf", "Performance Improvements"}, {"refactor", "Code Refactoring"},
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(`
|
||||
# feat: Features
|
||||
# fix: Bug Fixes
|
||||
# perf: Performance Improvements
|
||||
# refactor: Code Refactoring`, f.TitleMapsString())
|
||||
|
||||
f = &CommitMessageFormat{
|
||||
patternMaps: []string{},
|
||||
}
|
||||
|
||||
assert.Equal(" []", f.TitleMapsString())
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
package main
|
||||
|
||||
// Version of git-chglog cli client
|
||||
const Version = "0.0.1"
|
||||
|
|
@ -21,7 +21,7 @@ func (e *commitExtractor) Extract(commits []*Commit) ([]*CommitGroup, []*Commit,
|
|||
mergeCommits := []*Commit{}
|
||||
revertCommits := []*Commit{}
|
||||
|
||||
filteredCommits := commitFilter(commits, e.opts.CommitFilters)
|
||||
filteredCommits := commitFilter(commits, e.opts.CommitFilters, e.opts.NoCaseSensitive)
|
||||
|
||||
for _, commit := range commits {
|
||||
if commit.Merge != nil {
|
||||
|
|
@ -37,7 +37,7 @@ func (e *commitExtractor) Extract(commits []*Commit) ([]*CommitGroup, []*Commit,
|
|||
|
||||
for _, commit := range filteredCommits {
|
||||
if commit.Merge == nil && commit.Revert == nil {
|
||||
e.processCommitGroups(&commitGroups, commit)
|
||||
e.processCommitGroups(&commitGroups, commit, e.opts.NoCaseSensitive)
|
||||
}
|
||||
|
||||
e.processNoteGroups(¬eGroups, commit)
|
||||
|
|
@ -49,14 +49,23 @@ func (e *commitExtractor) Extract(commits []*Commit) ([]*CommitGroup, []*Commit,
|
|||
return commitGroups, mergeCommits, revertCommits, noteGroups
|
||||
}
|
||||
|
||||
func (e *commitExtractor) processCommitGroups(groups *[]*CommitGroup, commit *Commit) {
|
||||
func (e *commitExtractor) processCommitGroups(groups *[]*CommitGroup, commit *Commit, noCaseSensitive bool) {
|
||||
var group *CommitGroup
|
||||
|
||||
// commit group
|
||||
raw, ttl := e.commitGroupTitle(commit)
|
||||
|
||||
for _, g := range *groups {
|
||||
if g.RawTitle == raw {
|
||||
rawTitleTmp := g.RawTitle
|
||||
if noCaseSensitive {
|
||||
rawTitleTmp = strings.ToLower(g.RawTitle)
|
||||
}
|
||||
|
||||
rawTmp := raw
|
||||
if noCaseSensitive {
|
||||
rawTmp = strings.ToLower(raw)
|
||||
}
|
||||
if rawTitleTmp == rawTmp {
|
||||
group = g
|
||||
}
|
||||
}
|
||||
|
|
@ -110,6 +119,7 @@ func (e *commitExtractor) commitGroupTitle(commit *Commit) (string, string) {
|
|||
if t, ok := e.opts.CommitGroupTitleMaps[v]; ok {
|
||||
ttl = t
|
||||
} else {
|
||||
//nolint:staticcheck
|
||||
ttl = strings.Title(raw)
|
||||
}
|
||||
}
|
||||
|
|
@ -118,9 +128,27 @@ func (e *commitExtractor) commitGroupTitle(commit *Commit) (string, string) {
|
|||
return raw, ttl
|
||||
}
|
||||
|
||||
func (e *commitExtractor) sortCommitGroups(groups []*CommitGroup) {
|
||||
func (e *commitExtractor) sortCommitGroups(groups []*CommitGroup) { //nolint:gocyclo
|
||||
// NOTE(khos2ow): this function is over our cyclomatic complexity goal.
|
||||
// Be wary when adding branches, and look for functionality that could
|
||||
// be reasonably moved into an injected dependency.
|
||||
|
||||
order := make(map[string]int)
|
||||
if e.opts.CommitGroupSortBy == "Custom" {
|
||||
for i, t := range e.opts.CommitGroupTitleOrder {
|
||||
order[t] = i
|
||||
}
|
||||
}
|
||||
|
||||
// groups
|
||||
// TODO(khos2ow): move the inline sort function to
|
||||
// conceret implementation of sort.Interface in order
|
||||
// to reduce cyclomatic complaxity.
|
||||
sort.Slice(groups, func(i, j int) bool {
|
||||
if e.opts.CommitGroupSortBy == "Custom" {
|
||||
return order[groups[i].RawTitle] < order[groups[j].RawTitle]
|
||||
}
|
||||
|
||||
var (
|
||||
a, b interface{}
|
||||
ok bool
|
||||
|
|
@ -145,6 +173,11 @@ func (e *commitExtractor) sortCommitGroups(groups []*CommitGroup) {
|
|||
|
||||
// commits
|
||||
for _, group := range groups {
|
||||
group := group // pin group to avoid potential bugs with passing group to lower functions
|
||||
|
||||
// TODO(khos2ow): move the inline sort function to
|
||||
// conceret implementation of sort.Interface in order
|
||||
// to reduce cyclomatic complaxity.
|
||||
sort.Slice(group.Commits, func(i, j int) bool {
|
||||
var (
|
||||
a, b interface{}
|
||||
|
|
@ -178,6 +211,7 @@ func (e *commitExtractor) sortNoteGroups(groups []*NoteGroup) {
|
|||
|
||||
// notes
|
||||
for _, group := range groups {
|
||||
group := group // pin group to avoid potential bugs with passing group to lower functions
|
||||
sort.Slice(group.Notes, func(i, j int) bool {
|
||||
return strings.ToLower(group.Notes[i].Title) < strings.ToLower(group.Notes[j].Title)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -20,14 +20,14 @@ func TestCommitExtractor(t *testing.T) {
|
|||
|
||||
fixtures := []*Commit{
|
||||
// [0]
|
||||
&Commit{
|
||||
{
|
||||
Type: "foo",
|
||||
Scope: "c",
|
||||
Header: "1",
|
||||
Notes: []*Note{},
|
||||
},
|
||||
// [1]
|
||||
&Commit{
|
||||
{
|
||||
Type: "foo",
|
||||
Scope: "b",
|
||||
Header: "2",
|
||||
|
|
@ -37,7 +37,7 @@ func TestCommitExtractor(t *testing.T) {
|
|||
},
|
||||
},
|
||||
// [2]
|
||||
&Commit{
|
||||
{
|
||||
Type: "bar",
|
||||
Scope: "d",
|
||||
Header: "3",
|
||||
|
|
@ -47,7 +47,7 @@ func TestCommitExtractor(t *testing.T) {
|
|||
},
|
||||
},
|
||||
// [3]
|
||||
&Commit{
|
||||
{
|
||||
Type: "foo",
|
||||
Scope: "a",
|
||||
Header: "4",
|
||||
|
|
@ -56,7 +56,7 @@ func TestCommitExtractor(t *testing.T) {
|
|||
},
|
||||
},
|
||||
// [4]
|
||||
&Commit{
|
||||
{
|
||||
Type: "",
|
||||
Scope: "",
|
||||
Header: "Merge1",
|
||||
|
|
@ -67,7 +67,7 @@ func TestCommitExtractor(t *testing.T) {
|
|||
},
|
||||
},
|
||||
// [5]
|
||||
&Commit{
|
||||
{
|
||||
Type: "",
|
||||
Scope: "",
|
||||
Header: "Revert1",
|
||||
|
|
@ -81,14 +81,14 @@ func TestCommitExtractor(t *testing.T) {
|
|||
commitGroups, mergeCommits, revertCommits, noteGroups := extractor.Extract(fixtures)
|
||||
|
||||
assert.Equal([]*CommitGroup{
|
||||
&CommitGroup{
|
||||
{
|
||||
RawTitle: "bar",
|
||||
Title: "BAR",
|
||||
Commits: []*Commit{
|
||||
fixtures[2],
|
||||
},
|
||||
},
|
||||
&CommitGroup{
|
||||
{
|
||||
RawTitle: "foo",
|
||||
Title: "Foo",
|
||||
Commits: []*Commit{
|
||||
|
|
@ -108,26 +108,157 @@ func TestCommitExtractor(t *testing.T) {
|
|||
}, revertCommits)
|
||||
|
||||
assert.Equal([]*NoteGroup{
|
||||
&NoteGroup{
|
||||
{
|
||||
Title: "note1-title",
|
||||
Notes: []*Note{
|
||||
fixtures[1].Notes[0],
|
||||
fixtures[2].Notes[0],
|
||||
},
|
||||
},
|
||||
&NoteGroup{
|
||||
{
|
||||
Title: "note2-title",
|
||||
Notes: []*Note{
|
||||
fixtures[1].Notes[1],
|
||||
},
|
||||
},
|
||||
&NoteGroup{
|
||||
{
|
||||
Title: "note3-title",
|
||||
Notes: []*Note{
|
||||
fixtures[2].Notes[1],
|
||||
},
|
||||
},
|
||||
&NoteGroup{
|
||||
{
|
||||
Title: "note4-title",
|
||||
Notes: []*Note{
|
||||
fixtures[3].Notes[0],
|
||||
},
|
||||
},
|
||||
}, noteGroups)
|
||||
}
|
||||
|
||||
func TestCommitOrderExtractor(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
extractor := newCommitExtractor(&Options{
|
||||
CommitSortBy: "Scope",
|
||||
CommitGroupBy: "Type",
|
||||
CommitGroupSortBy: "Custom",
|
||||
CommitGroupTitleOrder: []string{"foo", "bar"},
|
||||
CommitGroupTitleMaps: map[string]string{
|
||||
"bar": "BAR",
|
||||
},
|
||||
})
|
||||
|
||||
fixtures := []*Commit{
|
||||
// [0]
|
||||
{
|
||||
Type: "foo",
|
||||
Scope: "c",
|
||||
Header: "1",
|
||||
Notes: []*Note{},
|
||||
},
|
||||
// [1]
|
||||
{
|
||||
Type: "foo",
|
||||
Scope: "b",
|
||||
Header: "2",
|
||||
Notes: []*Note{
|
||||
{"note1-title", "note1-body"},
|
||||
{"note2-title", "note2-body"},
|
||||
},
|
||||
},
|
||||
// [2]
|
||||
{
|
||||
Type: "bar",
|
||||
Scope: "d",
|
||||
Header: "3",
|
||||
Notes: []*Note{
|
||||
{"note1-title", "note1-body"},
|
||||
{"note3-title", "note3-body"},
|
||||
},
|
||||
},
|
||||
// [3]
|
||||
{
|
||||
Type: "foo",
|
||||
Scope: "a",
|
||||
Header: "4",
|
||||
Notes: []*Note{
|
||||
{"note4-title", "note4-body"},
|
||||
},
|
||||
},
|
||||
// [4]
|
||||
{
|
||||
Type: "",
|
||||
Scope: "",
|
||||
Header: "Merge1",
|
||||
Notes: []*Note{},
|
||||
Merge: &Merge{
|
||||
Ref: "123",
|
||||
Source: "merges/merge1",
|
||||
},
|
||||
},
|
||||
// [5]
|
||||
{
|
||||
Type: "",
|
||||
Scope: "",
|
||||
Header: "Revert1",
|
||||
Notes: []*Note{},
|
||||
Revert: &Revert{
|
||||
Header: "REVERT1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
commitGroups, mergeCommits, revertCommits, noteGroups := extractor.Extract(fixtures)
|
||||
|
||||
assert.Equal([]*CommitGroup{
|
||||
{
|
||||
RawTitle: "foo",
|
||||
Title: "Foo",
|
||||
Commits: []*Commit{
|
||||
fixtures[3],
|
||||
fixtures[1],
|
||||
fixtures[0],
|
||||
},
|
||||
},
|
||||
{
|
||||
RawTitle: "bar",
|
||||
Title: "BAR",
|
||||
Commits: []*Commit{
|
||||
fixtures[2],
|
||||
},
|
||||
},
|
||||
}, commitGroups)
|
||||
|
||||
assert.Equal([]*Commit{
|
||||
fixtures[4],
|
||||
}, mergeCommits)
|
||||
|
||||
assert.Equal([]*Commit{
|
||||
fixtures[5],
|
||||
}, revertCommits)
|
||||
|
||||
assert.Equal([]*NoteGroup{
|
||||
{
|
||||
Title: "note1-title",
|
||||
Notes: []*Note{
|
||||
fixtures[1].Notes[0],
|
||||
fixtures[2].Notes[0],
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "note2-title",
|
||||
Notes: []*Note{
|
||||
fixtures[1].Notes[1],
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "note3-title",
|
||||
Notes: []*Note{
|
||||
fixtures[2].Notes[1],
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "note4-title",
|
||||
Notes: []*Note{
|
||||
fixtures[3].Notes[0],
|
||||
|
|
|
|||
|
|
@ -1,6 +1,14 @@
|
|||
package chglog
|
||||
|
||||
func commitFilter(commits []*Commit, filters map[string][]string) []*Commit {
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func commitFilter(commits []*Commit, filters map[string][]string, noCaseSensitive bool) []*Commit { //nolint:gocyclo
|
||||
// NOTE(khos2ow): this function is over our cyclomatic complexity goal.
|
||||
// Be wary when adding branches, and look for functionality that could
|
||||
// be reasonably moved into an injected dependency.
|
||||
|
||||
res := []*Commit{}
|
||||
|
||||
for _, commit := range commits {
|
||||
|
|
@ -23,9 +31,17 @@ func commitFilter(commits []*Commit, filters map[string][]string) []*Commit {
|
|||
break
|
||||
}
|
||||
|
||||
if noCaseSensitive {
|
||||
str = strings.ToLower(str)
|
||||
}
|
||||
|
||||
exist := false
|
||||
|
||||
for _, val := range values {
|
||||
if noCaseSensitive {
|
||||
val = strings.ToLower(val)
|
||||
}
|
||||
|
||||
if str == val {
|
||||
exist = true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,26 +18,31 @@ func TestCommitFilter(t *testing.T) {
|
|||
}
|
||||
|
||||
fixtures := []*Commit{
|
||||
&Commit{
|
||||
{
|
||||
Type: "foo",
|
||||
Scope: "hoge",
|
||||
Subject: "1",
|
||||
},
|
||||
&Commit{
|
||||
{
|
||||
Type: "foo",
|
||||
Scope: "fuga",
|
||||
Subject: "2",
|
||||
},
|
||||
&Commit{
|
||||
{
|
||||
Type: "bar",
|
||||
Scope: "hoge",
|
||||
Subject: "3",
|
||||
},
|
||||
&Commit{
|
||||
{
|
||||
Type: "bar",
|
||||
Scope: "fuga",
|
||||
Subject: "4",
|
||||
},
|
||||
{
|
||||
Type: "Bar",
|
||||
Scope: "hogera",
|
||||
Subject: "5",
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(
|
||||
|
|
@ -46,8 +51,9 @@ func TestCommitFilter(t *testing.T) {
|
|||
"2",
|
||||
"3",
|
||||
"4",
|
||||
"5",
|
||||
},
|
||||
pickCommitSubjects(commitFilter(fixtures, map[string][]string{})),
|
||||
pickCommitSubjects(commitFilter(fixtures, map[string][]string{}, false)),
|
||||
)
|
||||
|
||||
assert.Equal(
|
||||
|
|
@ -59,7 +65,20 @@ func TestCommitFilter(t *testing.T) {
|
|||
},
|
||||
pickCommitSubjects(commitFilter(fixtures, map[string][]string{
|
||||
"Type": {"foo", "bar"},
|
||||
})),
|
||||
}, false)),
|
||||
)
|
||||
|
||||
assert.Equal(
|
||||
[]string{
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"4",
|
||||
"5",
|
||||
},
|
||||
pickCommitSubjects(commitFilter(fixtures, map[string][]string{
|
||||
"Type": {"foo", "bar"},
|
||||
}, true)),
|
||||
)
|
||||
|
||||
assert.Equal(
|
||||
|
|
@ -69,7 +88,7 @@ func TestCommitFilter(t *testing.T) {
|
|||
},
|
||||
pickCommitSubjects(commitFilter(fixtures, map[string][]string{
|
||||
"Type": {"foo"},
|
||||
})),
|
||||
}, false)),
|
||||
)
|
||||
|
||||
assert.Equal(
|
||||
|
|
@ -79,7 +98,18 @@ func TestCommitFilter(t *testing.T) {
|
|||
},
|
||||
pickCommitSubjects(commitFilter(fixtures, map[string][]string{
|
||||
"Type": {"bar"},
|
||||
})),
|
||||
}, false)),
|
||||
)
|
||||
|
||||
assert.Equal(
|
||||
[]string{
|
||||
"3",
|
||||
"4",
|
||||
"5",
|
||||
},
|
||||
pickCommitSubjects(commitFilter(fixtures, map[string][]string{
|
||||
"Type": {"bar"},
|
||||
}, true)),
|
||||
)
|
||||
|
||||
assert.Equal(
|
||||
|
|
@ -89,7 +119,7 @@ func TestCommitFilter(t *testing.T) {
|
|||
},
|
||||
pickCommitSubjects(commitFilter(fixtures, map[string][]string{
|
||||
"Scope": {"fuga"},
|
||||
})),
|
||||
}, false)),
|
||||
)
|
||||
|
||||
assert.Equal(
|
||||
|
|
@ -99,7 +129,7 @@ func TestCommitFilter(t *testing.T) {
|
|||
pickCommitSubjects(commitFilter(fixtures, map[string][]string{
|
||||
"Type": {"bar"},
|
||||
"Scope": {"hoge"},
|
||||
})),
|
||||
}, false)),
|
||||
)
|
||||
|
||||
assert.Equal(
|
||||
|
|
@ -110,6 +140,6 @@ func TestCommitFilter(t *testing.T) {
|
|||
pickCommitSubjects(commitFilter(fixtures, map[string][]string{
|
||||
"Type": {"foo"},
|
||||
"Scope": {"fuga", "hoge"},
|
||||
})),
|
||||
}, false)),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
178
commit_parser.go
178
commit_parser.go
|
|
@ -1,12 +1,13 @@
|
|||
package chglog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
gitcmd "github.com/tsuyoshiwada/go-gitcmd"
|
||||
"github.com/tsuyoshiwada/go-gitcmd"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -47,18 +48,23 @@ func joinAndQuoteMeta(list []string, sep string) string {
|
|||
}
|
||||
|
||||
type commitParser struct {
|
||||
client gitcmd.Client
|
||||
config *Config
|
||||
reHeader *regexp.Regexp
|
||||
reMerge *regexp.Regexp
|
||||
reRevert *regexp.Regexp
|
||||
reRef *regexp.Regexp
|
||||
reIssue *regexp.Regexp
|
||||
reNotes *regexp.Regexp
|
||||
reMention *regexp.Regexp
|
||||
logger *Logger
|
||||
client gitcmd.Client
|
||||
jiraClient JiraClient
|
||||
config *Config
|
||||
reHeader *regexp.Regexp
|
||||
reMerge *regexp.Regexp
|
||||
reRevert *regexp.Regexp
|
||||
reRef *regexp.Regexp
|
||||
reIssue *regexp.Regexp
|
||||
reNotes *regexp.Regexp
|
||||
reMention *regexp.Regexp
|
||||
reSignOff *regexp.Regexp
|
||||
reCoAuthor *regexp.Regexp
|
||||
reJiraIssueDescription *regexp.Regexp
|
||||
}
|
||||
|
||||
func newCommitParser(client gitcmd.Client, config *Config) *commitParser {
|
||||
func newCommitParser(logger *Logger, client gitcmd.Client, jiraClient JiraClient, config *Config) *commitParser {
|
||||
opts := config.Options
|
||||
|
||||
joinedRefActions := joinAndQuoteMeta(opts.RefActions, "|")
|
||||
|
|
@ -66,25 +72,38 @@ func newCommitParser(client gitcmd.Client, config *Config) *commitParser {
|
|||
joinedNoteKeywords := joinAndQuoteMeta(opts.NoteKeywords, "|")
|
||||
|
||||
return &commitParser{
|
||||
client: client,
|
||||
config: config,
|
||||
reHeader: regexp.MustCompile(opts.HeaderPattern),
|
||||
reMerge: regexp.MustCompile(opts.MergePattern),
|
||||
reRevert: regexp.MustCompile(opts.RevertPattern),
|
||||
reRef: regexp.MustCompile("(?i)(" + joinedRefActions + ")\\s?([\\w/\\.\\-]+)?(?:" + joinedIssuePrefix + ")(\\d+)"),
|
||||
reIssue: regexp.MustCompile("(?:" + joinedIssuePrefix + ")(\\d+)"),
|
||||
reNotes: regexp.MustCompile("^(?i)\\s*(" + joinedNoteKeywords + ")[:\\s]+(.*)"),
|
||||
reMention: regexp.MustCompile("@([\\w-]+)"),
|
||||
logger: logger,
|
||||
client: client,
|
||||
jiraClient: jiraClient,
|
||||
config: config,
|
||||
reHeader: regexp.MustCompile(opts.HeaderPattern),
|
||||
reMerge: regexp.MustCompile(opts.MergePattern),
|
||||
reRevert: regexp.MustCompile(opts.RevertPattern),
|
||||
reRef: regexp.MustCompile("(?i)(" + joinedRefActions + ")\\s?([\\w/\\.\\-]+)?(?:" + joinedIssuePrefix + ")(\\d+)"),
|
||||
reIssue: regexp.MustCompile("(?:" + joinedIssuePrefix + ")(\\d+)"),
|
||||
reNotes: regexp.MustCompile("^(?i)\\s*(" + joinedNoteKeywords + ")[:\\s]+(.*)"),
|
||||
reMention: regexp.MustCompile(`@([\w-]+)`),
|
||||
reSignOff: regexp.MustCompile(`Signed-off-by:\s+([\p{L}\s\-\[\]]+)\s+<([\w+\-\[\].@]+)>`),
|
||||
reCoAuthor: regexp.MustCompile(`Co-authored-by:\s+([\p{L}\s\-\[\]]+)\s+<([\w+\-\[\].@]+)>`),
|
||||
reJiraIssueDescription: regexp.MustCompile(opts.JiraIssueDescriptionPattern),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *commitParser) Parse(rev string) ([]*Commit, error) {
|
||||
out, err := p.client.Exec(
|
||||
"log",
|
||||
paths := p.config.Options.Paths
|
||||
|
||||
args := []string{
|
||||
rev,
|
||||
"--no-decorate",
|
||||
"--pretty="+logFormat,
|
||||
)
|
||||
"--pretty=" + logFormat,
|
||||
}
|
||||
|
||||
if len(paths) > 0 {
|
||||
args = append(args, "--")
|
||||
args = append(args, paths...)
|
||||
}
|
||||
|
||||
out, err := p.client.Exec("log", args...)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -206,6 +225,41 @@ func (p *commitParser) processHeader(commit *Commit, input string) {
|
|||
// refs & mentions
|
||||
commit.Refs = p.parseRefs(input)
|
||||
commit.Mentions = p.parseMentions(input)
|
||||
|
||||
// Jira
|
||||
if commit.JiraIssueID != "" {
|
||||
p.processJiraIssue(commit, commit.JiraIssueID)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *commitParser) extractLineMetadata(commit *Commit, line string) bool {
|
||||
meta := false
|
||||
|
||||
refs := p.parseRefs(line)
|
||||
if len(refs) > 0 {
|
||||
meta = true
|
||||
commit.Refs = append(commit.Refs, refs...)
|
||||
}
|
||||
|
||||
mentions := p.parseMentions(line)
|
||||
if len(mentions) > 0 {
|
||||
meta = true
|
||||
commit.Mentions = append(commit.Mentions, mentions...)
|
||||
}
|
||||
|
||||
coAuthors := p.parseCoAuthors(line)
|
||||
if len(coAuthors) > 0 {
|
||||
meta = true
|
||||
commit.CoAuthors = append(commit.CoAuthors, coAuthors...)
|
||||
}
|
||||
|
||||
signers := p.parseSigners(line)
|
||||
if len(signers) > 0 {
|
||||
meta = true
|
||||
commit.Signers = append(commit.Signers, signers...)
|
||||
}
|
||||
|
||||
return meta
|
||||
}
|
||||
|
||||
func (p *commitParser) processBody(commit *Commit, input string) {
|
||||
|
|
@ -217,30 +271,29 @@ func (p *commitParser) processBody(commit *Commit, input string) {
|
|||
// notes & refs & mentions
|
||||
commit.Notes = []*Note{}
|
||||
inNote := false
|
||||
trim := false
|
||||
fenceDetector := newMdFenceDetector()
|
||||
lines := strings.Split(input, "\n")
|
||||
|
||||
// body without notes & refs & mentions
|
||||
trimmedBody := make([]string, 0, len(lines))
|
||||
|
||||
for _, line := range lines {
|
||||
if !inNote {
|
||||
trim = false
|
||||
}
|
||||
fenceDetector.Update(line)
|
||||
|
||||
if !fenceDetector.InCodeblock() {
|
||||
refs := p.parseRefs(line)
|
||||
if len(refs) > 0 {
|
||||
inNote = false
|
||||
commit.Refs = append(commit.Refs, refs...)
|
||||
}
|
||||
|
||||
mentions := p.parseMentions(line)
|
||||
if len(mentions) > 0 {
|
||||
inNote = false
|
||||
commit.Mentions = append(commit.Mentions, mentions...)
|
||||
}
|
||||
if !fenceDetector.InCodeblock() && p.extractLineMetadata(commit, line) {
|
||||
trim = true
|
||||
inNote = false
|
||||
}
|
||||
|
||||
// Q: should this check also only be outside of code blocks?
|
||||
res := p.reNotes.FindAllStringSubmatch(line, -1)
|
||||
|
||||
if len(res) > 0 {
|
||||
inNote = true
|
||||
trim = true
|
||||
for _, r := range res {
|
||||
commit.Notes = append(commit.Notes, &Note{
|
||||
Title: r[1],
|
||||
|
|
@ -251,8 +304,13 @@ func (p *commitParser) processBody(commit *Commit, input string) {
|
|||
last := commit.Notes[len(commit.Notes)-1]
|
||||
last.Body = last.Body + "\n" + line
|
||||
}
|
||||
|
||||
if !trim {
|
||||
trimmedBody = append(trimmedBody, line)
|
||||
}
|
||||
}
|
||||
|
||||
commit.TrimmedBody = strings.TrimSpace(strings.Join(trimmedBody, "\n"))
|
||||
p.trimSpaceInNotes(commit)
|
||||
}
|
||||
|
||||
|
|
@ -297,6 +355,30 @@ func (p *commitParser) parseRefs(input string) []*Ref {
|
|||
return refs
|
||||
}
|
||||
|
||||
func (p *commitParser) parseSigners(input string) []Contact {
|
||||
res := p.reSignOff.FindAllStringSubmatch(input, -1)
|
||||
contacts := make([]Contact, len(res))
|
||||
|
||||
for i, r := range res {
|
||||
contacts[i].Name = r[1]
|
||||
contacts[i].Email = r[2]
|
||||
}
|
||||
|
||||
return contacts
|
||||
}
|
||||
|
||||
func (p *commitParser) parseCoAuthors(input string) []Contact {
|
||||
res := p.reCoAuthor.FindAllStringSubmatch(input, -1)
|
||||
contacts := make([]Contact, len(res))
|
||||
|
||||
for i, r := range res {
|
||||
contacts[i].Name = r[1]
|
||||
contacts[i].Email = r[2]
|
||||
}
|
||||
|
||||
return contacts
|
||||
}
|
||||
|
||||
func (p *commitParser) parseMentions(input string) []string {
|
||||
res := p.reMention.FindAllStringSubmatch(input, -1)
|
||||
mentions := make([]string, len(res))
|
||||
|
|
@ -344,6 +426,28 @@ func (p *commitParser) uniqMentions(mentions []string) []string {
|
|||
return arr
|
||||
}
|
||||
|
||||
func (p *commitParser) processJiraIssue(commit *Commit, issueID string) {
|
||||
issue, err := p.jiraClient.GetJiraIssue(commit.JiraIssueID)
|
||||
if err != nil {
|
||||
p.logger.Error(fmt.Sprintf("Failed to parse Jira story %s: %s\n", issueID, err))
|
||||
return
|
||||
}
|
||||
commit.Type = p.config.Options.JiraTypeMaps[issue.Fields.Type.Name]
|
||||
commit.JiraIssue = &JiraIssue{
|
||||
Type: issue.Fields.Type.Name,
|
||||
Summary: issue.Fields.Summary,
|
||||
Description: issue.Fields.Description,
|
||||
Labels: issue.Fields.Labels,
|
||||
}
|
||||
|
||||
if p.config.Options.JiraIssueDescriptionPattern != "" {
|
||||
res := p.reJiraIssueDescription.FindStringSubmatch(commit.JiraIssue.Description)
|
||||
if len(res) > 1 {
|
||||
commit.JiraIssue.Description = res[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
fenceTypes = []string{
|
||||
"```",
|
||||
|
|
|
|||
|
|
@ -3,11 +3,12 @@ package chglog
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
agjira "github.com/andygrunwald/go-jira"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
|
@ -21,62 +22,63 @@ func TestCommitParserParse(t *testing.T) {
|
|||
return "", errors.New("")
|
||||
}
|
||||
|
||||
bytes, _ := ioutil.ReadFile(filepath.Join("testdata", "gitlog.txt"))
|
||||
bytes, _ := os.ReadFile(filepath.Join("testdata", "gitlog.txt"))
|
||||
|
||||
return string(bytes), nil
|
||||
},
|
||||
}
|
||||
|
||||
parser := newCommitParser(mock, &Config{
|
||||
Options: &Options{
|
||||
CommitFilters: map[string][]string{
|
||||
"Type": []string{
|
||||
"feat",
|
||||
parser := newCommitParser(NewLogger(os.Stdout, os.Stderr, false, true),
|
||||
mock, nil, &Config{
|
||||
Options: &Options{
|
||||
CommitFilters: map[string][]string{
|
||||
"Type": {
|
||||
"feat",
|
||||
"fix",
|
||||
"perf",
|
||||
"refactor",
|
||||
},
|
||||
},
|
||||
HeaderPattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$",
|
||||
HeaderPatternMaps: []string{
|
||||
"Type",
|
||||
"Scope",
|
||||
"Subject",
|
||||
},
|
||||
IssuePrefix: []string{
|
||||
"#",
|
||||
"gh-",
|
||||
},
|
||||
RefActions: []string{
|
||||
"close",
|
||||
"closes",
|
||||
"closed",
|
||||
"fix",
|
||||
"perf",
|
||||
"refactor",
|
||||
"fixes",
|
||||
"fixed",
|
||||
"resolve",
|
||||
"resolves",
|
||||
"resolved",
|
||||
},
|
||||
MergePattern: "^Merge pull request #(\\d+) from (.*)$",
|
||||
MergePatternMaps: []string{
|
||||
"Ref",
|
||||
"Source",
|
||||
},
|
||||
RevertPattern: "^Revert \"([\\s\\S]*)\"$",
|
||||
RevertPatternMaps: []string{
|
||||
"Header",
|
||||
},
|
||||
NoteKeywords: []string{
|
||||
"BREAKING CHANGE",
|
||||
},
|
||||
},
|
||||
HeaderPattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$",
|
||||
HeaderPatternMaps: []string{
|
||||
"Type",
|
||||
"Scope",
|
||||
"Subject",
|
||||
},
|
||||
IssuePrefix: []string{
|
||||
"#",
|
||||
"gh-",
|
||||
},
|
||||
RefActions: []string{
|
||||
"close",
|
||||
"closes",
|
||||
"closed",
|
||||
"fix",
|
||||
"fixes",
|
||||
"fixed",
|
||||
"resolve",
|
||||
"resolves",
|
||||
"resolved",
|
||||
},
|
||||
MergePattern: "^Merge pull request #(\\d+) from (.*)$",
|
||||
MergePatternMaps: []string{
|
||||
"Ref",
|
||||
"Source",
|
||||
},
|
||||
RevertPattern: "^Revert \"([\\s\\S]*)\"$",
|
||||
RevertPatternMaps: []string{
|
||||
"Header",
|
||||
},
|
||||
NoteKeywords: []string{
|
||||
"BREAKING CHANGE",
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
commits, err := parser.Parse("HEAD")
|
||||
assert.Nil(err)
|
||||
assert.Equal([]*Commit{
|
||||
&Commit{
|
||||
{
|
||||
Hash: &Hash{
|
||||
Long: "65cf1add9735dcc4810dda3312b0792236c97c4e",
|
||||
Short: "65cf1add",
|
||||
|
|
@ -94,21 +96,22 @@ func TestCommitParserParse(t *testing.T) {
|
|||
Merge: nil,
|
||||
Revert: nil,
|
||||
Refs: []*Ref{
|
||||
&Ref{
|
||||
{
|
||||
Action: "",
|
||||
Ref: "123",
|
||||
Source: "",
|
||||
},
|
||||
},
|
||||
Notes: []*Note{},
|
||||
Mentions: []string{},
|
||||
Header: "feat(*): Add new feature #123",
|
||||
Type: "feat",
|
||||
Scope: "*",
|
||||
Subject: "Add new feature #123",
|
||||
Body: "",
|
||||
Notes: []*Note{},
|
||||
Mentions: []string{},
|
||||
Header: "feat(*): Add new feature #123",
|
||||
Type: "feat",
|
||||
Scope: "*",
|
||||
Subject: "Add new feature #123",
|
||||
Body: "",
|
||||
TrimmedBody: "",
|
||||
},
|
||||
&Commit{
|
||||
{
|
||||
Hash: &Hash{
|
||||
Long: "14ef0b6d386c5432af9292eab3c8314fa3001bc7",
|
||||
Short: "14ef0b6d",
|
||||
|
|
@ -129,24 +132,24 @@ func TestCommitParserParse(t *testing.T) {
|
|||
},
|
||||
Revert: nil,
|
||||
Refs: []*Ref{
|
||||
&Ref{
|
||||
{
|
||||
Action: "",
|
||||
Ref: "3",
|
||||
Source: "",
|
||||
},
|
||||
&Ref{
|
||||
{
|
||||
Action: "Fixes",
|
||||
Ref: "3",
|
||||
Source: "",
|
||||
},
|
||||
&Ref{
|
||||
{
|
||||
Action: "Closes",
|
||||
Ref: "1",
|
||||
Source: "",
|
||||
},
|
||||
},
|
||||
Notes: []*Note{
|
||||
&Note{
|
||||
{
|
||||
Title: "BREAKING CHANGE",
|
||||
Body: "This is breaking point message.",
|
||||
},
|
||||
|
|
@ -163,8 +166,9 @@ Fixes #3
|
|||
Closes #1
|
||||
|
||||
BREAKING CHANGE: This is breaking point message.`,
|
||||
TrimmedBody: `This is body message.`,
|
||||
},
|
||||
&Commit{
|
||||
{
|
||||
Hash: &Hash{
|
||||
Long: "809a8280ffd0dadb0f4e7ba9fc835e63c37d6af6",
|
||||
Short: "809a8280",
|
||||
|
|
@ -197,8 +201,9 @@ BREAKING CHANGE: This is breaking point message.`,
|
|||
@tsuyoshiwada
|
||||
@hogefuga
|
||||
@FooBarBaz`,
|
||||
TrimmedBody: `Has mention body`,
|
||||
},
|
||||
&Commit{
|
||||
{
|
||||
Hash: &Hash{
|
||||
Long: "74824d6bd1470b901ec7123d13a76a1b8938d8d0",
|
||||
Short: "74824d6b",
|
||||
|
|
@ -216,19 +221,19 @@ BREAKING CHANGE: This is breaking point message.`,
|
|||
Merge: nil,
|
||||
Revert: nil,
|
||||
Refs: []*Ref{
|
||||
&Ref{
|
||||
{
|
||||
Action: "Fixes",
|
||||
Ref: "123",
|
||||
Source: "",
|
||||
},
|
||||
&Ref{
|
||||
{
|
||||
Action: "Closes",
|
||||
Ref: "456",
|
||||
Source: "username/repository",
|
||||
},
|
||||
},
|
||||
Notes: []*Note{
|
||||
&Note{
|
||||
{
|
||||
Title: "BREAKING CHANGE",
|
||||
Body: fmt.Sprintf(`This is multiline breaking change note.
|
||||
It is treated as the body of the Note until a mention or reference appears.
|
||||
|
|
@ -277,8 +282,9 @@ class MyController extends Controller {
|
|||
|
||||
Fixes #123
|
||||
Closes username/repository#456`, "```", "```"),
|
||||
TrimmedBody: `This mixed body message.`,
|
||||
},
|
||||
&Commit{
|
||||
{
|
||||
Hash: &Hash{
|
||||
Long: "123456789735dcc4810dda3312b0792236c97c4e",
|
||||
Short: "12345678",
|
||||
|
|
@ -297,14 +303,114 @@ Closes username/repository#456`, "```", "```"),
|
|||
Revert: &Revert{
|
||||
Header: "fix(core): commit message",
|
||||
},
|
||||
Refs: []*Ref{},
|
||||
Notes: []*Note{},
|
||||
Mentions: []string{},
|
||||
Header: "Revert \"fix(core): commit message\"",
|
||||
Type: "",
|
||||
Scope: "",
|
||||
Subject: "",
|
||||
Body: "This reverts commit f755db78dcdf461dc42e709b3ab728ceba353d1d.",
|
||||
Refs: []*Ref{},
|
||||
Notes: []*Note{},
|
||||
Mentions: []string{},
|
||||
Header: "Revert \"fix(core): commit message\"",
|
||||
Type: "",
|
||||
Scope: "",
|
||||
Subject: "",
|
||||
Body: "This reverts commit f755db78dcdf461dc42e709b3ab728ceba353d1d.",
|
||||
TrimmedBody: "This reverts commit f755db78dcdf461dc42e709b3ab728ceba353d1d.",
|
||||
},
|
||||
}, commits)
|
||||
}
|
||||
|
||||
type mockJiraClient struct {
|
||||
}
|
||||
|
||||
func (jira mockJiraClient) GetJiraIssue(id string) (*agjira.Issue, error) {
|
||||
return &agjira.Issue{
|
||||
ID: id,
|
||||
Fields: &agjira.IssueFields{
|
||||
Expand: "",
|
||||
Type: agjira.IssueType{Name: "Story"},
|
||||
Project: agjira.Project{},
|
||||
Resolution: nil,
|
||||
Priority: nil,
|
||||
Resolutiondate: agjira.Time{},
|
||||
Created: agjira.Time{},
|
||||
Duedate: agjira.Date{},
|
||||
Watches: nil,
|
||||
Assignee: nil,
|
||||
Updated: agjira.Time{},
|
||||
Description: fmt.Sprintf("description of %s", id),
|
||||
Summary: fmt.Sprintf("summary of %s", id),
|
||||
Creator: nil,
|
||||
Reporter: nil,
|
||||
Components: nil,
|
||||
Status: nil,
|
||||
Progress: nil,
|
||||
AggregateProgress: nil,
|
||||
TimeTracking: nil,
|
||||
TimeSpent: 0,
|
||||
TimeEstimate: 0,
|
||||
TimeOriginalEstimate: 0,
|
||||
Worklog: nil,
|
||||
IssueLinks: nil,
|
||||
Comments: nil,
|
||||
FixVersions: nil,
|
||||
AffectsVersions: nil,
|
||||
Labels: []string{"GA"},
|
||||
Subtasks: nil,
|
||||
Attachments: nil,
|
||||
Epic: nil,
|
||||
Sprint: nil,
|
||||
Parent: nil,
|
||||
AggregateTimeOriginalEstimate: 0,
|
||||
AggregateTimeSpent: 0,
|
||||
AggregateTimeEstimate: 0,
|
||||
Unknowns: nil,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func TestCommitParserParseWithJira(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
assert.True(true)
|
||||
|
||||
mock := &mockClient{
|
||||
ReturnExec: func(subcmd string, args ...string) (string, error) {
|
||||
if subcmd != "log" {
|
||||
return "", errors.New("")
|
||||
}
|
||||
|
||||
bytes, _ := os.ReadFile(filepath.Join("testdata", "gitlog_jira.txt"))
|
||||
|
||||
return string(bytes), nil
|
||||
},
|
||||
}
|
||||
|
||||
parser := newCommitParser(NewLogger(os.Stdout, os.Stderr, false, true),
|
||||
mock, mockJiraClient{}, &Config{
|
||||
Options: &Options{
|
||||
CommitFilters: map[string][]string{
|
||||
"Type": {
|
||||
"feat",
|
||||
"fix",
|
||||
"perf",
|
||||
"refactor",
|
||||
},
|
||||
},
|
||||
HeaderPattern: "^(?:(\\w*)|(?:\\[(.*)\\])?)\\:\\s(.*)$",
|
||||
HeaderPatternMaps: []string{
|
||||
"Type",
|
||||
"JiraIssueID",
|
||||
"Subject",
|
||||
},
|
||||
JiraTypeMaps: map[string]string{
|
||||
"Story": "feat",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
commits, err := parser.Parse("HEAD")
|
||||
assert.Nil(err)
|
||||
commit := commits[0]
|
||||
assert.Equal(commit.JiraIssueID, "JIRA-1111")
|
||||
assert.Equal(commit.JiraIssue.Type, "Story")
|
||||
assert.Equal(commit.JiraIssue.Summary, "summary of JIRA-1111")
|
||||
assert.Equal(commit.JiraIssue.Description, "description of JIRA-1111")
|
||||
assert.Equal(commit.JiraIssue.Labels, []string{"GA"})
|
||||
assert.Equal(commit.Type, "feat")
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 532 KiB After Width: | Height: | Size: 828 KiB |
|
|
@ -4,56 +4,58 @@ import (
|
|||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
func Example() {
|
||||
gen := NewGenerator(&Config{
|
||||
Bin: "git",
|
||||
WorkingDir: ".",
|
||||
Template: "CHANGELOG.tpl.md",
|
||||
Info: &Info{
|
||||
Title: "CHANGELOG",
|
||||
RepositoryURL: "https://github.com/git-chglog/git-chglog",
|
||||
},
|
||||
Options: &Options{
|
||||
CommitFilters: map[string][]string{
|
||||
"Type": []string{
|
||||
"feat",
|
||||
"fix",
|
||||
gen := NewGenerator(NewLogger(os.Stdout, os.Stderr, false, true),
|
||||
&Config{
|
||||
Bin: "git",
|
||||
WorkingDir: ".",
|
||||
Template: "CHANGELOG.tpl.md",
|
||||
Info: &Info{
|
||||
Title: "CHANGELOG",
|
||||
RepositoryURL: "https://github.com/git-chglog/git-chglog",
|
||||
},
|
||||
Options: &Options{
|
||||
CommitFilters: map[string][]string{
|
||||
"Type": {
|
||||
"feat",
|
||||
"fix",
|
||||
},
|
||||
},
|
||||
CommitSortBy: "Scope",
|
||||
CommitGroupBy: "Type",
|
||||
CommitGroupSortBy: "Title",
|
||||
CommitGroupTitleMaps: map[string]string{
|
||||
"feat": "Features",
|
||||
"fix": "Bug Fixes",
|
||||
},
|
||||
HeaderPattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$",
|
||||
HeaderPatternMaps: []string{
|
||||
"Type",
|
||||
"Scope",
|
||||
"Subject",
|
||||
},
|
||||
IssuePrefix: []string{
|
||||
"#",
|
||||
"gh-",
|
||||
},
|
||||
RefActions: []string{},
|
||||
MergePattern: "^Merge pull request #(\\d+) from (.*)$",
|
||||
MergePatternMaps: []string{
|
||||
"Ref",
|
||||
"Source",
|
||||
},
|
||||
RevertPattern: "^Revert \"([\\s\\S]*)\"$",
|
||||
RevertPatternMaps: []string{
|
||||
"Header",
|
||||
},
|
||||
NoteKeywords: []string{
|
||||
"BREAKING CHANGE",
|
||||
},
|
||||
},
|
||||
CommitSortBy: "Scope",
|
||||
CommitGroupBy: "Type",
|
||||
CommitGroupSortBy: "Title",
|
||||
CommitGroupTitleMaps: map[string]string{
|
||||
"feat": "Features",
|
||||
"fix": "Bug Fixes",
|
||||
},
|
||||
HeaderPattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$",
|
||||
HeaderPatternMaps: []string{
|
||||
"Type",
|
||||
"Scope",
|
||||
"Subject",
|
||||
},
|
||||
IssuePrefix: []string{
|
||||
"#",
|
||||
"gh-",
|
||||
},
|
||||
RefActions: []string{},
|
||||
MergePattern: "^Merge pull request #(\\d+) from (.*)$",
|
||||
MergePatternMaps: []string{
|
||||
"Ref",
|
||||
"Source",
|
||||
},
|
||||
RevertPattern: "^Revert \"([\\s\\S]*)\"$",
|
||||
RevertPatternMaps: []string{
|
||||
"Header",
|
||||
},
|
||||
NoteKeywords: []string{
|
||||
"BREAKING CHANGE",
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
err := gen.Generate(buf, "")
|
||||
|
|
|
|||
60
fields.go
60
fields.go
|
|
@ -8,6 +8,12 @@ type Hash struct {
|
|||
Short string
|
||||
}
|
||||
|
||||
// Contact of co-authors and signers
|
||||
type Contact struct {
|
||||
Name string
|
||||
Email string
|
||||
}
|
||||
|
||||
// Author of commit
|
||||
type Author struct {
|
||||
Name string
|
||||
|
|
@ -52,21 +58,34 @@ type NoteGroup struct {
|
|||
Notes []*Note
|
||||
}
|
||||
|
||||
// JiraIssue is information about a jira ticket (type, summary, description, and labels)
|
||||
type JiraIssue struct {
|
||||
Type string
|
||||
Summary string
|
||||
Description string
|
||||
Labels []string
|
||||
}
|
||||
|
||||
// Commit data
|
||||
type Commit struct {
|
||||
Hash *Hash
|
||||
Author *Author
|
||||
Committer *Committer
|
||||
Merge *Merge // If it is not a merge commit, `nil` is assigned
|
||||
Revert *Revert // If it is not a revert commit, `nil` is assigned
|
||||
Refs []*Ref
|
||||
Notes []*Note
|
||||
Mentions []string // Name of the user included in the commit header or body
|
||||
Header string // (e.g. `feat(core): Add new feature`)
|
||||
Type string // (e.g. `feat`)
|
||||
Scope string // (e.g. `core`)
|
||||
Subject string // (e.g. `Add new feature`)
|
||||
Body string
|
||||
Hash *Hash
|
||||
Author *Author
|
||||
Committer *Committer
|
||||
Merge *Merge // If it is not a merge commit, `nil` is assigned
|
||||
Revert *Revert // If it is not a revert commit, `nil` is assigned
|
||||
Refs []*Ref
|
||||
Notes []*Note
|
||||
Mentions []string // Name of the user included in the commit header or body
|
||||
CoAuthors []Contact // (e.g. `Co-authored-by: user <user@email>`)
|
||||
Signers []Contact // (e.g. `Signed-off-by: user <user@email>`)
|
||||
JiraIssue *JiraIssue // If no issue id found in header, `nil` is assigned
|
||||
Header string // (e.g. `feat(core)[RNWY-310]: Add new feature`)
|
||||
Type string // (e.g. `feat`)
|
||||
Scope string // (e.g. `core`)
|
||||
Subject string // (e.g. `Add new feature`)
|
||||
JiraIssueID string // (e.g. `RNWY-310`)
|
||||
Body string
|
||||
TrimmedBody string // Body without any Notes/Refs/Mentions/CoAuthors/Signers
|
||||
}
|
||||
|
||||
// CommitGroup is a collection of commits grouped according to the `CommitGroupBy` option
|
||||
|
|
@ -80,13 +99,15 @@ type CommitGroup struct {
|
|||
// If you give `Tag`, the reference hierarchy will be deepened.
|
||||
// This struct is used to minimize the hierarchy of references
|
||||
type RelateTag struct {
|
||||
Name string
|
||||
Date time.Time
|
||||
Name string
|
||||
Subject string
|
||||
Date time.Time
|
||||
}
|
||||
|
||||
// Tag is data of git-tag
|
||||
type Tag struct {
|
||||
Name string
|
||||
Subject string
|
||||
Date time.Time
|
||||
Next *RelateTag
|
||||
Previous *RelateTag
|
||||
|
|
@ -101,3 +122,12 @@ type Version struct {
|
|||
RevertCommits []*Commit
|
||||
NoteGroups []*NoteGroup
|
||||
}
|
||||
|
||||
// Unreleased is unreleased commit dataset
|
||||
type Unreleased struct {
|
||||
CommitGroups []*CommitGroup
|
||||
Commits []*Commit
|
||||
MergeCommits []*Commit
|
||||
RevertCommits []*Commit
|
||||
NoteGroups []*NoteGroup
|
||||
}
|
||||
|
|
|
|||
48
go.mod
Normal file
48
go.mod
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
module github.com/git-chglog/git-chglog
|
||||
|
||||
go 1.24.1
|
||||
|
||||
require (
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7
|
||||
github.com/Masterminds/sprig/v3 v3.3.0
|
||||
github.com/andygrunwald/go-jira v1.16.0
|
||||
github.com/coreos/go-semver v0.3.1
|
||||
github.com/fatih/color v1.18.0
|
||||
github.com/imdario/mergo v0.3.16
|
||||
github.com/kyokomi/emoji/v2 v2.2.13
|
||||
github.com/mattn/go-colorable v0.1.14
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/tsuyoshiwada/go-gitcmd v0.0.0-20180205145712-5f1f5f9475df
|
||||
github.com/urfave/cli/v2 v2.27.6
|
||||
gopkg.in/yaml.v2 v2.3.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.1 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.3.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fatih/structs v1.1.0 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/huandu/xstrings v1.5.0 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
github.com/spf13/cast v1.7.0 // indirect
|
||||
github.com/trivago/tgo v1.0.7 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
||||
golang.org/x/crypto v0.26.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/term v0.23.0 // indirect
|
||||
golang.org/x/text v0.17.0 // indirect
|
||||
)
|
||||
135
go.sum
Normal file
135
go.sum
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
|
||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=
|
||||
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
|
||||
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
|
||||
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
|
||||
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
|
||||
github.com/andygrunwald/go-jira v1.16.0 h1:PU7C7Fkk5L96JvPc6vDVIrd99vdPnYudHu4ju2c2ikQ=
|
||||
github.com/andygrunwald/go-jira v1.16.0/go.mod h1:UQH4IBVxIYWbgagc0LF/k9FRs9xjIiQ8hIcC6HfLwFU=
|
||||
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
|
||||
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
|
||||
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
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/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
|
||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
|
||||
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
|
||||
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
|
||||
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kyokomi/emoji/v2 v2.2.13 h1:GhTfQa67venUUvmleTNFnb+bi7S3aocF7ZCXU9fSO7U=
|
||||
github.com/kyokomi/emoji/v2 v2.2.13/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
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/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
|
||||
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/trivago/tgo v1.0.7 h1:uaWH/XIy9aWYWpjm2CU3RpcqZXmX2ysQ9/Go+d9gyrM=
|
||||
github.com/trivago/tgo v1.0.7/go.mod h1:w4dpD+3tzNIIiIfkWWa85w5/B77tlvdZckQ+6PkFnhc=
|
||||
github.com/tsuyoshiwada/go-gitcmd v0.0.0-20180205145712-5f1f5f9475df h1:Y2l28Jr3vOEeYtxfVbMtVfOdAwuUqWaP9fvNKiBVeXY=
|
||||
github.com/tsuyoshiwada/go-gitcmd v0.0.0-20180205145712-5f1f5f9475df/go.mod h1:pnyouUty/nBr/zm3GYwTIt+qFTLWbdjeLjZmJdzJOu8=
|
||||
github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g=
|
||||
github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
|
||||
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
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.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
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=
|
||||
38
jira.go
Normal file
38
jira.go
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
package chglog
|
||||
|
||||
import (
|
||||
agjira "github.com/andygrunwald/go-jira"
|
||||
)
|
||||
|
||||
// JiraClient is an HTTP client for Jira
|
||||
type JiraClient interface {
|
||||
GetJiraIssue(id string) (*agjira.Issue, error)
|
||||
}
|
||||
|
||||
type jiraClient struct {
|
||||
username string
|
||||
token string
|
||||
url string
|
||||
}
|
||||
|
||||
// NewJiraClient returns an instance of JiraClient
|
||||
func NewJiraClient(config *Config) JiraClient {
|
||||
return jiraClient{
|
||||
username: config.Options.JiraUsername,
|
||||
token: config.Options.JiraToken,
|
||||
url: config.Options.JiraURL,
|
||||
}
|
||||
}
|
||||
|
||||
func (jira jiraClient) GetJiraIssue(id string) (*agjira.Issue, error) {
|
||||
tp := agjira.BasicAuthTransport{
|
||||
Username: jira.username,
|
||||
Password: jira.token,
|
||||
}
|
||||
client, err := agjira.NewClient(tp.Client(), jira.url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
issue, _, err := client.Issue.Get(id, nil)
|
||||
return issue, err
|
||||
}
|
||||
43
jira_test.go
Normal file
43
jira_test.go
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
package chglog
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestJira(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
config := &Config{
|
||||
Options: &Options{
|
||||
Processor: nil,
|
||||
NextTag: "",
|
||||
TagFilterPattern: "",
|
||||
CommitFilters: nil,
|
||||
CommitSortBy: "",
|
||||
CommitGroupBy: "",
|
||||
CommitGroupSortBy: "",
|
||||
CommitGroupTitleMaps: nil,
|
||||
HeaderPattern: "",
|
||||
HeaderPatternMaps: nil,
|
||||
IssuePrefix: nil,
|
||||
RefActions: nil,
|
||||
MergePattern: "",
|
||||
MergePatternMaps: nil,
|
||||
RevertPattern: "",
|
||||
RevertPatternMaps: nil,
|
||||
NoteKeywords: nil,
|
||||
JiraUsername: "uuu",
|
||||
JiraToken: "ppp",
|
||||
JiraURL: "http://jira.com",
|
||||
JiraTypeMaps: nil,
|
||||
JiraIssueDescriptionPattern: "",
|
||||
},
|
||||
}
|
||||
|
||||
jira := NewJiraClient(config)
|
||||
issue, err := jira.GetJiraIssue("fake")
|
||||
assert.Nil(issue)
|
||||
assert.Error(err)
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package main
|
||||
package chglog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
|
@ -7,7 +7,7 @@ import (
|
|||
"regexp"
|
||||
|
||||
"github.com/fatih/color"
|
||||
emoji "gopkg.in/kyokomi/emoji.v1"
|
||||
"github.com/kyokomi/emoji/v2"
|
||||
)
|
||||
|
||||
// Logger ...
|
||||
|
|
@ -26,7 +26,7 @@ func NewLogger(stdout, stderr io.Writer, silent, noEmoji bool) *Logger {
|
|||
stderr: stderr,
|
||||
silent: silent,
|
||||
noEmoji: noEmoji,
|
||||
reEmoji: regexp.MustCompile(":[\\w\\+_\\-]+:\\s?"),
|
||||
reEmoji: regexp.MustCompile(`:[\w\+_\-]+:\s?`),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package main
|
||||
package chglog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
|
@ -6,8 +6,8 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/fatih/color"
|
||||
emoji "github.com/kyokomi/emoji/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
emoji "gopkg.in/kyokomi/emoji.v1"
|
||||
)
|
||||
|
||||
func TestLoggerLogSilent(t *testing.T) {
|
||||
|
|
@ -40,7 +40,7 @@ func TestLoggerLogNoEmoji(t *testing.T) {
|
|||
stderr := &bytes.Buffer{}
|
||||
logger := NewLogger(stdout, stderr, false, true)
|
||||
logger.Log(":+1:Hello, World! :)")
|
||||
assert.Equal(fmt.Sprint("Hello, World! :)\n"), stdout.String())
|
||||
assert.Equal(fmt.Sprint("Hello, World! :)\n"), stdout.String()) //nolint:gosimple
|
||||
}
|
||||
|
||||
func TestLoggerError(t *testing.T) {
|
||||
130
processor.go
130
processor.go
|
|
@ -14,8 +14,8 @@ type Processor interface {
|
|||
// GitHubProcessor is optimized for CHANGELOG used in GitHub
|
||||
//
|
||||
// The following processing is performed
|
||||
// - Mentions automatic link (@tsuyoshiwada -> [@tsuyoshiwada](https://github.com/tsuyoshiwada))
|
||||
// - Automatic link to references (#123 -> [#123](https://github.com/owner/repo/issues/123))
|
||||
// - Mentions automatic link (@tsuyoshiwada -> [@tsuyoshiwada](https://github.com/tsuyoshiwada))
|
||||
// - Automatic link to references (#123 -> [#123](https://github.com/owner/repo/issues/123))
|
||||
type GitHubProcessor struct {
|
||||
Host string // Host name used for link destination. Note: You must include the protocol (e.g. "https://github.com")
|
||||
config *Config
|
||||
|
|
@ -33,8 +33,8 @@ func (p *GitHubProcessor) Bootstrap(config *Config) {
|
|||
p.Host = strings.TrimRight(p.Host, "/")
|
||||
}
|
||||
|
||||
p.reMention = regexp.MustCompile("@(\\w+)")
|
||||
p.reIssue = regexp.MustCompile("(?i)(#|gh-)(\\d+)")
|
||||
p.reMention = regexp.MustCompile(`@(\w+)`)
|
||||
p.reIssue = regexp.MustCompile(`(?i)(#|gh-)(\d+)`)
|
||||
}
|
||||
|
||||
// ProcessCommit ...
|
||||
|
|
@ -42,9 +42,15 @@ func (p *GitHubProcessor) ProcessCommit(commit *Commit) *Commit {
|
|||
commit.Header = p.addLinks(commit.Header)
|
||||
commit.Subject = p.addLinks(commit.Subject)
|
||||
commit.Body = p.addLinks(commit.Body)
|
||||
|
||||
for _, note := range commit.Notes {
|
||||
note.Body = p.addLinks(note.Body)
|
||||
}
|
||||
|
||||
if commit.Revert != nil {
|
||||
commit.Revert.Header = p.addLinks(commit.Revert.Header)
|
||||
}
|
||||
|
||||
return commit
|
||||
}
|
||||
|
||||
|
|
@ -59,3 +65,119 @@ func (p *GitHubProcessor) addLinks(input string) string {
|
|||
|
||||
return input
|
||||
}
|
||||
|
||||
// GitLabProcessor is optimized for CHANGELOG used in GitLab
|
||||
//
|
||||
// The following processing is performed
|
||||
// - Mentions automatic link (@tsuyoshiwada -> [@tsuyoshiwada](https://gitlab.com/tsuyoshiwada))
|
||||
// - Automatic link to references issues (#123 -> [#123](https://gitlab.com/owner/repo/issues/123))
|
||||
// - Automatic link to references merge request (!123 -> [#123](https://gitlab.com/owner/repo/merge_requests/123))
|
||||
type GitLabProcessor struct {
|
||||
Host string // Host name used for link destination. Note: You must include the protocol (e.g. "https://gitlab.com")
|
||||
config *Config
|
||||
reMention *regexp.Regexp
|
||||
reIssue *regexp.Regexp
|
||||
reMergeRequest *regexp.Regexp
|
||||
}
|
||||
|
||||
// Bootstrap ...
|
||||
func (p *GitLabProcessor) Bootstrap(config *Config) {
|
||||
p.config = config
|
||||
|
||||
if p.Host == "" {
|
||||
p.Host = "https://gitlab.com"
|
||||
} else {
|
||||
p.Host = strings.TrimRight(p.Host, "/")
|
||||
}
|
||||
|
||||
p.reMention = regexp.MustCompile(`@(\w+)`)
|
||||
p.reIssue = regexp.MustCompile(`(?i)#(\d+)`)
|
||||
p.reMergeRequest = regexp.MustCompile(`(?i)!(\d+)`)
|
||||
}
|
||||
|
||||
// ProcessCommit ...
|
||||
func (p *GitLabProcessor) ProcessCommit(commit *Commit) *Commit {
|
||||
commit.Header = p.addLinks(commit.Header)
|
||||
commit.Subject = p.addLinks(commit.Subject)
|
||||
commit.Body = p.addLinks(commit.Body)
|
||||
|
||||
for _, note := range commit.Notes {
|
||||
note.Body = p.addLinks(note.Body)
|
||||
}
|
||||
|
||||
if commit.Revert != nil {
|
||||
commit.Revert.Header = p.addLinks(commit.Revert.Header)
|
||||
}
|
||||
|
||||
return commit
|
||||
}
|
||||
|
||||
func (p *GitLabProcessor) addLinks(input string) string {
|
||||
repoURL := strings.TrimRight(p.config.Info.RepositoryURL, "/")
|
||||
|
||||
// mentions
|
||||
input = p.reMention.ReplaceAllString(input, "[@$1]("+p.Host+"/$1)")
|
||||
|
||||
// issues
|
||||
input = p.reIssue.ReplaceAllString(input, "[#$1]("+repoURL+"/issues/$1)")
|
||||
|
||||
// merge requests
|
||||
input = p.reMergeRequest.ReplaceAllString(input, "[!$1]("+repoURL+"/merge_requests/$1)")
|
||||
|
||||
return input
|
||||
}
|
||||
|
||||
// BitbucketProcessor is optimized for CHANGELOG used in Bitbucket
|
||||
//
|
||||
// The following processing is performed
|
||||
// - Mentions automatic link (@tsuyoshiwada -> [@tsuyoshiwada](https://bitbucket.org/tsuyoshiwada/))
|
||||
// - Automatic link to references (#123 -> [#123](https://bitbucket.org/owner/repo/issues/123/))
|
||||
type BitbucketProcessor struct {
|
||||
Host string // Host name used for link destination. Note: You must include the protocol (e.g. "https://bitbucket.org")
|
||||
config *Config
|
||||
reMention *regexp.Regexp
|
||||
reIssue *regexp.Regexp
|
||||
}
|
||||
|
||||
// Bootstrap ...
|
||||
func (p *BitbucketProcessor) Bootstrap(config *Config) {
|
||||
p.config = config
|
||||
|
||||
if p.Host == "" {
|
||||
p.Host = "https://bitbucket.org"
|
||||
} else {
|
||||
p.Host = strings.TrimRight(p.Host, "/")
|
||||
}
|
||||
|
||||
p.reMention = regexp.MustCompile(`@(\w+)`)
|
||||
p.reIssue = regexp.MustCompile(`(?i)#(\d+)`)
|
||||
}
|
||||
|
||||
// ProcessCommit ...
|
||||
func (p *BitbucketProcessor) ProcessCommit(commit *Commit) *Commit {
|
||||
commit.Header = p.addLinks(commit.Header)
|
||||
commit.Subject = p.addLinks(commit.Subject)
|
||||
commit.Body = p.addLinks(commit.Body)
|
||||
|
||||
for _, note := range commit.Notes {
|
||||
note.Body = p.addLinks(note.Body)
|
||||
}
|
||||
|
||||
if commit.Revert != nil {
|
||||
commit.Revert.Header = p.addLinks(commit.Revert.Header)
|
||||
}
|
||||
|
||||
return commit
|
||||
}
|
||||
|
||||
func (p *BitbucketProcessor) addLinks(input string) string {
|
||||
repoURL := strings.TrimRight(p.config.Info.RepositoryURL, "/")
|
||||
|
||||
// mentions
|
||||
input = p.reMention.ReplaceAllString(input, "[@$1]("+p.Host+"/$1/)")
|
||||
|
||||
// issues
|
||||
input = p.reIssue.ReplaceAllString(input, "[#$1]("+repoURL+"/issues/$1/)")
|
||||
|
||||
return input
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ func TestGitHubProcessor(t *testing.T) {
|
|||
multiline [#789](https://example.com/issues/789)
|
||||
[@foo](https://github.com/foo), [@bar](https://github.com/bar)`,
|
||||
Notes: []*Note{
|
||||
&Note{
|
||||
{
|
||||
Body: `issue1 [#11](https://example.com/issues/11)
|
||||
issue2 [#22](https://example.com/issues/22)
|
||||
[gh-56](https://example.com/issues/56) hoge fuga`,
|
||||
|
|
@ -42,7 +42,7 @@ issue2 [#22](https://example.com/issues/22)
|
|||
multiline #789
|
||||
@foo, @bar`,
|
||||
Notes: []*Note{
|
||||
&Note{
|
||||
{
|
||||
Body: `issue1 #11
|
||||
issue2 #22
|
||||
gh-56 hoge fuga`,
|
||||
|
|
@ -51,4 +51,147 @@ gh-56 hoge fuga`,
|
|||
},
|
||||
),
|
||||
)
|
||||
|
||||
assert.Equal(
|
||||
&Commit{
|
||||
Revert: &Revert{
|
||||
Header: "revert header [@mention](https://github.com/mention) [#123](https://example.com/issues/123)",
|
||||
},
|
||||
},
|
||||
processor.ProcessCommit(
|
||||
&Commit{
|
||||
Revert: &Revert{
|
||||
Header: "revert header @mention #123",
|
||||
},
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func TestGitLabProcessor(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
config := &Config{
|
||||
Info: &Info{
|
||||
RepositoryURL: "https://example.com",
|
||||
},
|
||||
}
|
||||
|
||||
processor := &GitLabProcessor{}
|
||||
|
||||
processor.Bootstrap(config)
|
||||
|
||||
assert.Equal(
|
||||
&Commit{
|
||||
Header: "message [@foo](https://gitlab.com/foo) [#123](https://example.com/issues/123) [!345](https://example.com/merge_requests/345)",
|
||||
Subject: "message [@foo](https://gitlab.com/foo) [#123](https://example.com/issues/123) [!345](https://example.com/merge_requests/345)",
|
||||
Body: `issue [#456](https://example.com/issues/456)
|
||||
multiline [#789](https://example.com/issues/789)
|
||||
merge request [!345](https://example.com/merge_requests/345)
|
||||
[@foo](https://gitlab.com/foo), [@bar](https://gitlab.com/bar)`,
|
||||
Notes: []*Note{
|
||||
{
|
||||
Body: `issue1 [#11](https://example.com/issues/11) [!33](https://example.com/merge_requests/33)
|
||||
issue2 [#22](https://example.com/issues/22)
|
||||
merge request [!33](https://example.com/merge_requests/33)
|
||||
gh-56 hoge fuga`,
|
||||
},
|
||||
},
|
||||
},
|
||||
processor.ProcessCommit(
|
||||
&Commit{
|
||||
Header: "message @foo #123 !345",
|
||||
Subject: "message @foo #123 !345",
|
||||
Body: `issue #456
|
||||
multiline #789
|
||||
merge request !345
|
||||
@foo, @bar`,
|
||||
Notes: []*Note{
|
||||
{
|
||||
Body: `issue1 #11 !33
|
||||
issue2 #22
|
||||
merge request !33
|
||||
gh-56 hoge fuga`,
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
assert.Equal(
|
||||
&Commit{
|
||||
Revert: &Revert{
|
||||
Header: "revert header [@mention](https://gitlab.com/mention) [#123](https://example.com/issues/123) [!345](https://example.com/merge_requests/345)",
|
||||
},
|
||||
},
|
||||
processor.ProcessCommit(
|
||||
&Commit{
|
||||
Revert: &Revert{
|
||||
Header: "revert header @mention #123 !345",
|
||||
},
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func TestBitbucketProcessor(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
config := &Config{
|
||||
Info: &Info{
|
||||
RepositoryURL: "https://example.com",
|
||||
},
|
||||
}
|
||||
|
||||
processor := &BitbucketProcessor{}
|
||||
|
||||
processor.Bootstrap(config)
|
||||
|
||||
assert.Equal(
|
||||
&Commit{
|
||||
Header: "message [@foo](https://bitbucket.org/foo/) [#123](https://example.com/issues/123/)",
|
||||
Subject: "message [@foo](https://bitbucket.org/foo/) [#123](https://example.com/issues/123/)",
|
||||
Body: `issue [#456](https://example.com/issues/456/)
|
||||
multiline [#789](https://example.com/issues/789/)
|
||||
[@foo](https://bitbucket.org/foo/), [@bar](https://bitbucket.org/bar/)`,
|
||||
Notes: []*Note{
|
||||
{
|
||||
Body: `issue1 [#11](https://example.com/issues/11/)
|
||||
issue2 [#22](https://example.com/issues/22/)
|
||||
gh-56 hoge fuga`,
|
||||
},
|
||||
},
|
||||
},
|
||||
processor.ProcessCommit(
|
||||
&Commit{
|
||||
Header: "message @foo #123",
|
||||
Subject: "message @foo #123",
|
||||
Body: `issue #456
|
||||
multiline #789
|
||||
@foo, @bar`,
|
||||
Notes: []*Note{
|
||||
{
|
||||
Body: `issue1 #11
|
||||
issue2 #22
|
||||
gh-56 hoge fuga`,
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
assert.Equal(
|
||||
&Commit{
|
||||
Revert: &Revert{
|
||||
Header: "revert header [@mention](https://bitbucket.org/mention/) [#123](https://example.com/issues/123/)",
|
||||
},
|
||||
},
|
||||
processor.ProcessCommit(
|
||||
&Commit{
|
||||
Revert: &Revert{
|
||||
Header: "revert header @mention #123",
|
||||
},
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
129
tag_reader.go
129
tag_reader.go
|
|
@ -1,66 +1,119 @@
|
|||
package chglog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/go-semver/semver"
|
||||
gitcmd "github.com/tsuyoshiwada/go-gitcmd"
|
||||
)
|
||||
|
||||
type tagReader struct {
|
||||
client gitcmd.Client
|
||||
format string
|
||||
reTag *regexp.Regexp
|
||||
client gitcmd.Client
|
||||
separator string
|
||||
reFilter *regexp.Regexp
|
||||
sortBy string
|
||||
}
|
||||
|
||||
func newTagReader(client gitcmd.Client) *tagReader {
|
||||
func newTagReader(client gitcmd.Client, filterPattern string, sort string) *tagReader {
|
||||
return &tagReader{
|
||||
client: client,
|
||||
reTag: regexp.MustCompile("tag: ([\\w\\.\\-_]+),?"),
|
||||
client: client,
|
||||
separator: "@@__CHGLOG__@@",
|
||||
reFilter: regexp.MustCompile(filterPattern),
|
||||
sortBy: sort,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *tagReader) ReadAll() ([]*Tag, error) {
|
||||
out, err := r.client.Exec(
|
||||
"log",
|
||||
"--tags",
|
||||
"--simplify-by-decoration",
|
||||
"--pretty=%D\t%at",
|
||||
"for-each-ref",
|
||||
"--format",
|
||||
"%(refname)"+r.separator+"%(subject)"+r.separator+"%(taggerdate)"+r.separator+"%(authordate)",
|
||||
"refs/tags",
|
||||
)
|
||||
|
||||
tags := []*Tag{}
|
||||
|
||||
if err != nil {
|
||||
return tags, err
|
||||
return tags, fmt.Errorf("failed to get git-tag: %w", err)
|
||||
}
|
||||
|
||||
lines := strings.Split(out, "\n")
|
||||
|
||||
for _, line := range lines {
|
||||
if line == "" {
|
||||
tokens := strings.Split(line, r.separator)
|
||||
|
||||
if len(tokens) != 4 {
|
||||
continue
|
||||
}
|
||||
|
||||
tokens := strings.Split(line, "\t")
|
||||
|
||||
res := r.reTag.FindAllStringSubmatch(tokens[0], -1)
|
||||
if len(res) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
ts, err := strconv.Atoi(tokens[1])
|
||||
name := r.parseRefname(tokens[0])
|
||||
subject := r.parseSubject(tokens[1])
|
||||
date, err := r.parseDate(tokens[2])
|
||||
if err != nil {
|
||||
continue
|
||||
t, err2 := r.parseDate(tokens[3])
|
||||
if err2 != nil {
|
||||
return nil, err2
|
||||
}
|
||||
date = t
|
||||
}
|
||||
|
||||
if r.reFilter != nil {
|
||||
if !r.reFilter.MatchString(name) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
tags = append(tags, &Tag{
|
||||
Name: res[0][1],
|
||||
Date: time.Unix(int64(ts), 0),
|
||||
Name: name,
|
||||
Subject: subject,
|
||||
Date: date,
|
||||
})
|
||||
}
|
||||
|
||||
switch r.sortBy {
|
||||
case "date":
|
||||
r.sortTags(tags)
|
||||
case "semver":
|
||||
r.filterSemVerTags(&tags)
|
||||
r.sortTagsBySemver(tags)
|
||||
}
|
||||
r.assignPreviousAndNextTag(tags)
|
||||
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
func (*tagReader) filterSemVerTags(tags *[]*Tag) {
|
||||
// filter out any non-semver tags
|
||||
for i, t := range *tags {
|
||||
// remove leading v, since its so
|
||||
// common.
|
||||
name := strings.TrimPrefix(t.Name, "v")
|
||||
|
||||
// attempt semver parse, if not successful
|
||||
// remove it from tags slice.
|
||||
if _, err := semver.NewVersion(name); err != nil {
|
||||
*tags = append((*tags)[:i], (*tags)[i+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (*tagReader) parseRefname(input string) string {
|
||||
return strings.Replace(input, "refs/tags/", "", 1)
|
||||
}
|
||||
|
||||
func (*tagReader) parseSubject(input string) string {
|
||||
return strings.TrimSpace(input)
|
||||
}
|
||||
|
||||
func (*tagReader) parseDate(input string) (time.Time, error) {
|
||||
return time.Parse("Mon Jan 2 15:04:05 2006 -0700", input)
|
||||
}
|
||||
|
||||
func (*tagReader) assignPreviousAndNextTag(tags []*Tag) {
|
||||
total := len(tags)
|
||||
|
||||
for i, tag := range tags {
|
||||
|
|
@ -71,21 +124,37 @@ func (r *tagReader) ReadAll() ([]*Tag, error) {
|
|||
|
||||
if i > 0 {
|
||||
next = &RelateTag{
|
||||
Name: tags[i-1].Name,
|
||||
Date: tags[i-1].Date,
|
||||
Name: tags[i-1].Name,
|
||||
Subject: tags[i-1].Subject,
|
||||
Date: tags[i-1].Date,
|
||||
}
|
||||
}
|
||||
|
||||
if i+1 < total {
|
||||
prev = &RelateTag{
|
||||
Name: tags[i+1].Name,
|
||||
Date: tags[i+1].Date,
|
||||
Name: tags[i+1].Name,
|
||||
Subject: tags[i+1].Subject,
|
||||
Date: tags[i+1].Date,
|
||||
}
|
||||
}
|
||||
|
||||
tag.Next = next
|
||||
tag.Previous = prev
|
||||
}
|
||||
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
func (*tagReader) sortTags(tags []*Tag) {
|
||||
sort.Slice(tags, func(i, j int) bool {
|
||||
return !tags[i].Date.Before(tags[j].Date)
|
||||
})
|
||||
}
|
||||
|
||||
func (*tagReader) sortTagsBySemver(tags []*Tag) {
|
||||
sort.Slice(tags, func(i, j int) bool {
|
||||
semver1 := strings.TrimPrefix(tags[i].Name, "v")
|
||||
semver2 := strings.TrimPrefix(tags[j].Name, "v")
|
||||
v1 := semver.New(semver1)
|
||||
v2 := semver.New(semver2)
|
||||
return v2.LessThan(*v1)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,95 +13,216 @@ func TestTagReader(t *testing.T) {
|
|||
assert := assert.New(t)
|
||||
client := &mockClient{
|
||||
ReturnExec: func(subcmd string, args ...string) (string, error) {
|
||||
if subcmd != "log" {
|
||||
if subcmd != "for-each-ref" {
|
||||
return "", errors.New("")
|
||||
}
|
||||
return strings.Join([]string{
|
||||
"",
|
||||
"tag: v5.2.0-beta.1, origin/labs/router\t1518023112",
|
||||
"tag: 2.0.0\t1517875200",
|
||||
"tag: v2.0.4-rc.1\t1517788800",
|
||||
"tag: 2.0.4-beta.1\t1517702400",
|
||||
"tag: hoge_fuga\t1517616000",
|
||||
"tag: 1.9.29-alpha.0\t1517529600",
|
||||
"hoge\t0",
|
||||
"foo\t0",
|
||||
"refs/tags/v2.0.4-beta.1@@__CHGLOG__@@Release v2.0.4-beta.1@@__CHGLOG__@@Thu Feb 1 00:00:00 2018 +0000@@__CHGLOG__@@",
|
||||
"refs/tags/4.4.3@@__CHGLOG__@@This is tag subject@@__CHGLOG__@@@@__CHGLOG__@@Fri Feb 2 00:00:00 2018 +0000",
|
||||
"refs/tags/4.4.4@@__CHGLOG__@@Release 4.4.4@@__CHGLOG__@@Fri Feb 2 10:00:40 2018 +0000@@__CHGLOG__@@",
|
||||
"refs/tags/v2.0.4-beta.2@@__CHGLOG__@@Release v2.0.4-beta.2@@__CHGLOG__@@Sat Feb 3 12:15:00 2018 +0000@@__CHGLOG__@@",
|
||||
"refs/tags/5.0.0-rc.0@@__CHGLOG__@@Release 5.0.0-rc.0@@__CHGLOG__@@Sat Feb 3 12:30:10 2018 +0000@@__CHGLOG__@@",
|
||||
"refs/tags/hoge_fuga@@__CHGLOG__@@Invalid semver tag name@@__CHGLOG__@@Mon Mar 12 12:30:10 2018 +0000@@__CHGLOG__@@",
|
||||
"hoge@@__CHGLOG__@@",
|
||||
}, "\n"), nil
|
||||
},
|
||||
}
|
||||
|
||||
actual, err := newTagReader(client).ReadAll()
|
||||
actual, err := newTagReader(client, "", "date").ReadAll()
|
||||
assert.Nil(err)
|
||||
|
||||
assert.Equal(
|
||||
[]*Tag{
|
||||
&Tag{
|
||||
Name: "v5.2.0-beta.1",
|
||||
Date: time.Unix(1518023112, 0),
|
||||
Next: nil,
|
||||
{
|
||||
Name: "hoge_fuga",
|
||||
Subject: "Invalid semver tag name",
|
||||
Date: time.Date(2018, 3, 12, 12, 30, 10, 0, time.UTC),
|
||||
Next: nil,
|
||||
Previous: &RelateTag{
|
||||
Name: "2.0.0",
|
||||
Date: time.Unix(1517875200, 0),
|
||||
Name: "5.0.0-rc.0",
|
||||
Subject: "Release 5.0.0-rc.0",
|
||||
Date: time.Date(2018, 2, 3, 12, 30, 10, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
&Tag{
|
||||
Name: "2.0.0",
|
||||
Date: time.Unix(1517875200, 0),
|
||||
{
|
||||
Name: "5.0.0-rc.0",
|
||||
Subject: "Release 5.0.0-rc.0",
|
||||
Date: time.Date(2018, 2, 3, 12, 30, 10, 0, time.UTC),
|
||||
Next: &RelateTag{
|
||||
Name: "v5.2.0-beta.1",
|
||||
Date: time.Unix(1518023112, 0),
|
||||
Name: "hoge_fuga",
|
||||
Subject: "Invalid semver tag name",
|
||||
Date: time.Date(2018, 3, 12, 12, 30, 10, 0, time.UTC),
|
||||
},
|
||||
Previous: &RelateTag{
|
||||
Name: "v2.0.4-rc.1",
|
||||
Date: time.Unix(1517788800, 0),
|
||||
Name: "v2.0.4-beta.2",
|
||||
Subject: "Release v2.0.4-beta.2",
|
||||
Date: time.Date(2018, 2, 3, 12, 15, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
&Tag{
|
||||
Name: "v2.0.4-rc.1",
|
||||
Date: time.Unix(1517788800, 0),
|
||||
{
|
||||
Name: "v2.0.4-beta.2",
|
||||
Subject: "Release v2.0.4-beta.2",
|
||||
Date: time.Date(2018, 2, 3, 12, 15, 0, 0, time.UTC),
|
||||
Next: &RelateTag{
|
||||
Name: "2.0.0",
|
||||
Date: time.Unix(1517875200, 0),
|
||||
Name: "5.0.0-rc.0",
|
||||
Subject: "Release 5.0.0-rc.0",
|
||||
Date: time.Date(2018, 2, 3, 12, 30, 10, 0, time.UTC),
|
||||
},
|
||||
Previous: &RelateTag{
|
||||
Name: "2.0.4-beta.1",
|
||||
Date: time.Unix(1517702400, 0),
|
||||
Name: "4.4.4",
|
||||
Subject: "Release 4.4.4",
|
||||
Date: time.Date(2018, 2, 2, 10, 0, 40, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
&Tag{
|
||||
Name: "2.0.4-beta.1",
|
||||
Date: time.Unix(1517702400, 0),
|
||||
{
|
||||
Name: "4.4.4",
|
||||
Subject: "Release 4.4.4",
|
||||
Date: time.Date(2018, 2, 2, 10, 0, 40, 0, time.UTC),
|
||||
Next: &RelateTag{
|
||||
Name: "v2.0.4-rc.1",
|
||||
Date: time.Unix(1517788800, 0),
|
||||
Name: "v2.0.4-beta.2",
|
||||
Subject: "Release v2.0.4-beta.2",
|
||||
Date: time.Date(2018, 2, 3, 12, 15, 0, 0, time.UTC),
|
||||
},
|
||||
Previous: &RelateTag{
|
||||
Name: "hoge_fuga",
|
||||
Date: time.Unix(1517616000, 0),
|
||||
Name: "4.4.3",
|
||||
Subject: "This is tag subject",
|
||||
Date: time.Date(2018, 2, 2, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
&Tag{
|
||||
Name: "hoge_fuga",
|
||||
Date: time.Unix(1517616000, 0),
|
||||
{
|
||||
Name: "4.4.3",
|
||||
Subject: "This is tag subject",
|
||||
Date: time.Date(2018, 2, 2, 0, 0, 0, 0, time.UTC),
|
||||
Next: &RelateTag{
|
||||
Name: "2.0.4-beta.1",
|
||||
Date: time.Unix(1517702400, 0),
|
||||
Name: "4.4.4",
|
||||
Subject: "Release 4.4.4",
|
||||
Date: time.Date(2018, 2, 2, 10, 0, 40, 0, time.UTC),
|
||||
},
|
||||
Previous: &RelateTag{
|
||||
Name: "1.9.29-alpha.0",
|
||||
Date: time.Unix(1517529600, 0),
|
||||
Name: "v2.0.4-beta.1",
|
||||
Subject: "Release v2.0.4-beta.1",
|
||||
Date: time.Date(2018, 2, 1, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
&Tag{
|
||||
Name: "1.9.29-alpha.0",
|
||||
Date: time.Unix(1517529600, 0),
|
||||
{
|
||||
Name: "v2.0.4-beta.1",
|
||||
Subject: "Release v2.0.4-beta.1",
|
||||
Date: time.Date(2018, 2, 1, 0, 0, 0, 0, time.UTC),
|
||||
Next: &RelateTag{
|
||||
Name: "hoge_fuga",
|
||||
Date: time.Unix(1517616000, 0),
|
||||
Name: "4.4.3",
|
||||
Subject: "This is tag subject",
|
||||
Date: time.Date(2018, 2, 2, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
Previous: nil,
|
||||
},
|
||||
},
|
||||
actual,
|
||||
)
|
||||
|
||||
actual, err = newTagReader(client, "", "semver").ReadAll()
|
||||
assert.Nil(err)
|
||||
|
||||
assert.Equal(
|
||||
[]*Tag{
|
||||
{
|
||||
Name: "5.0.0-rc.0",
|
||||
Subject: "Release 5.0.0-rc.0",
|
||||
Date: time.Date(2018, 2, 3, 12, 30, 10, 0, time.UTC),
|
||||
Next: nil,
|
||||
Previous: &RelateTag{
|
||||
Name: "4.4.4",
|
||||
Subject: "Release 4.4.4",
|
||||
Date: time.Date(2018, 2, 2, 10, 0, 40, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "4.4.4",
|
||||
Subject: "Release 4.4.4",
|
||||
Date: time.Date(2018, 2, 2, 10, 0, 40, 0, time.UTC),
|
||||
Next: &RelateTag{
|
||||
Name: "5.0.0-rc.0",
|
||||
Subject: "Release 5.0.0-rc.0",
|
||||
Date: time.Date(2018, 2, 3, 12, 30, 10, 0, time.UTC),
|
||||
},
|
||||
Previous: &RelateTag{
|
||||
Name: "4.4.3",
|
||||
Subject: "This is tag subject",
|
||||
Date: time.Date(2018, 2, 2, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "4.4.3",
|
||||
Subject: "This is tag subject",
|
||||
Date: time.Date(2018, 2, 2, 0, 0, 0, 0, time.UTC),
|
||||
Next: &RelateTag{
|
||||
Name: "4.4.4",
|
||||
Subject: "Release 4.4.4",
|
||||
Date: time.Date(2018, 2, 2, 10, 0, 40, 0, time.UTC),
|
||||
},
|
||||
Previous: &RelateTag{
|
||||
Name: "v2.0.4-beta.2",
|
||||
Subject: "Release v2.0.4-beta.2",
|
||||
Date: time.Date(2018, 2, 3, 12, 15, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "v2.0.4-beta.2",
|
||||
Subject: "Release v2.0.4-beta.2",
|
||||
Date: time.Date(2018, 2, 3, 12, 15, 0, 0, time.UTC),
|
||||
Next: &RelateTag{
|
||||
Name: "4.4.3",
|
||||
Subject: "This is tag subject",
|
||||
Date: time.Date(2018, 2, 2, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
Previous: &RelateTag{
|
||||
Name: "v2.0.4-beta.1",
|
||||
Subject: "Release v2.0.4-beta.1",
|
||||
Date: time.Date(2018, 2, 1, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "v2.0.4-beta.1",
|
||||
Subject: "Release v2.0.4-beta.1",
|
||||
Date: time.Date(2018, 2, 1, 0, 0, 0, 0, time.UTC),
|
||||
Next: &RelateTag{
|
||||
Name: "v2.0.4-beta.2",
|
||||
Subject: "Release v2.0.4-beta.2",
|
||||
Date: time.Date(2018, 2, 3, 12, 15, 0, 0, time.UTC),
|
||||
},
|
||||
Previous: nil,
|
||||
},
|
||||
},
|
||||
actual,
|
||||
)
|
||||
|
||||
actualFiltered, errFiltered := newTagReader(client, "^v", "date").ReadAll()
|
||||
assert.Nil(errFiltered)
|
||||
assert.Equal(
|
||||
[]*Tag{
|
||||
{
|
||||
Name: "v2.0.4-beta.2",
|
||||
Subject: "Release v2.0.4-beta.2",
|
||||
Date: time.Date(2018, 2, 3, 12, 15, 0, 0, time.UTC),
|
||||
Next: nil,
|
||||
Previous: &RelateTag{
|
||||
Name: "v2.0.4-beta.1",
|
||||
Subject: "Release v2.0.4-beta.1",
|
||||
Date: time.Date(2018, 2, 1, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "v2.0.4-beta.1",
|
||||
Subject: "Release v2.0.4-beta.1",
|
||||
Date: time.Date(2018, 2, 1, 0, 0, 0, 0, time.UTC),
|
||||
Next: &RelateTag{
|
||||
Name: "v2.0.4-beta.2",
|
||||
Subject: "Release v2.0.4-beta.2",
|
||||
Date: time.Date(2018, 2, 3, 12, 15, 0, 0, time.UTC),
|
||||
},
|
||||
Previous: nil,
|
||||
},
|
||||
},
|
||||
actualFiltered,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,14 +19,16 @@ func (s *tagSelector) Select(tags []*Tag, query string) ([]*Tag, string, error)
|
|||
case 2:
|
||||
old := tokens[0]
|
||||
new := tokens[1]
|
||||
if old == "" && new == "" {
|
||||
switch {
|
||||
case old == "" && new == "":
|
||||
return nil, "", nil
|
||||
} else if old == "" {
|
||||
case old == "":
|
||||
return s.selectBeforeTags(tags, new)
|
||||
} else if new == "" {
|
||||
case new == "":
|
||||
return s.selectAfterTags(tags, old)
|
||||
default:
|
||||
return s.selectRangeTags(tags, tokens[0], tokens[1])
|
||||
}
|
||||
return s.selectRangeTags(tags, tokens[0], tokens[1])
|
||||
}
|
||||
|
||||
return nil, "", errFailedQueryParse
|
||||
|
|
@ -76,7 +78,8 @@ func (*tagSelector) selectBeforeTags(tags []*Tag, token string) ([]*Tag, string,
|
|||
}
|
||||
|
||||
func (*tagSelector) selectAfterTags(tags []*Tag, token string) ([]*Tag, string, error) {
|
||||
var (
|
||||
// NOTE(clok): the res slice can range in size based on the token passed in.
|
||||
var ( //nolint:prealloc
|
||||
res []*Tag
|
||||
from string
|
||||
)
|
||||
|
|
|
|||
|
|
@ -12,51 +12,51 @@ func TestTagSelector(t *testing.T) {
|
|||
selector := newTagSelector()
|
||||
|
||||
fixtures := []*Tag{
|
||||
&Tag{Name: "2.2.12-rc.12"},
|
||||
&Tag{Name: "2.1.0"},
|
||||
&Tag{Name: "v2.0.0-beta.1"},
|
||||
&Tag{Name: "v1.2.9"},
|
||||
&Tag{Name: "v1.0.0"},
|
||||
{Name: "2.2.12-rc.12"},
|
||||
{Name: "2.1.0"},
|
||||
{Name: "v2.0.0-beta.1"},
|
||||
{Name: "v1.2.9"},
|
||||
{Name: "v1.0.0"},
|
||||
}
|
||||
|
||||
table := map[string][]string{
|
||||
// Single
|
||||
"2.2.12-rc.12": []string{
|
||||
"2.2.12-rc.12": {
|
||||
"2.2.12-rc.12",
|
||||
"2.1.0",
|
||||
},
|
||||
"v2.0.0-beta.1": []string{
|
||||
"v2.0.0-beta.1": {
|
||||
"v2.0.0-beta.1",
|
||||
"v1.2.9",
|
||||
},
|
||||
"v1.0.0": []string{
|
||||
"v1.0.0": {
|
||||
"v1.0.0",
|
||||
"",
|
||||
},
|
||||
// ~ <tag>
|
||||
"..2.1.0": []string{
|
||||
"..2.1.0": {
|
||||
"2.1.0",
|
||||
"v2.0.0-beta.1",
|
||||
"v1.2.9",
|
||||
"v1.0.0",
|
||||
"",
|
||||
},
|
||||
"..v1.0.0": []string{
|
||||
"..v1.0.0": {
|
||||
"v1.0.0",
|
||||
"",
|
||||
},
|
||||
// <tag> ~
|
||||
"v2.0.0-beta.1..": []string{
|
||||
"v2.0.0-beta.1..": {
|
||||
"2.2.12-rc.12",
|
||||
"2.1.0",
|
||||
"v2.0.0-beta.1",
|
||||
"v1.2.9",
|
||||
},
|
||||
"2.2.12-rc.12..": []string{
|
||||
"2.2.12-rc.12..": {
|
||||
"2.2.12-rc.12",
|
||||
"2.1.0",
|
||||
},
|
||||
"v1.0.0..": []string{
|
||||
"v1.0.0..": {
|
||||
"2.2.12-rc.12",
|
||||
"2.1.0",
|
||||
"v2.0.0-beta.1",
|
||||
|
|
@ -65,7 +65,7 @@ func TestTagSelector(t *testing.T) {
|
|||
"",
|
||||
},
|
||||
// <tag> ~ <tag>
|
||||
"v1.0.0..2.2.12-rc.12": []string{
|
||||
"v1.0.0..2.2.12-rc.12": {
|
||||
"2.2.12-rc.12",
|
||||
"2.1.0",
|
||||
"v2.0.0-beta.1",
|
||||
|
|
@ -73,13 +73,13 @@ func TestTagSelector(t *testing.T) {
|
|||
"v1.0.0",
|
||||
"",
|
||||
},
|
||||
"v1.0.0..v2.0.0-beta.1": []string{
|
||||
"v1.0.0..v2.0.0-beta.1": {
|
||||
"v2.0.0-beta.1",
|
||||
"v1.2.9",
|
||||
"v1.0.0",
|
||||
"",
|
||||
},
|
||||
"v1.2.9..2.1.0": []string{
|
||||
"v1.2.9..2.1.0": {
|
||||
"2.1.0",
|
||||
"v2.0.0-beta.1",
|
||||
"v1.2.9",
|
||||
|
|
|
|||
1
testdata/gitlog_jira.txt
vendored
Normal file
1
testdata/gitlog_jira.txt
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
@@__CHGLOG__@@HASH:65cf1add9735dcc4810dda3312b0792236c97c4e 65cf1add@@__CHGLOG_DELIMITER__@@AUTHOR:tsuyoshi wada mail@example.com 1514808000@@__CHGLOG_DELIMITER__@@COMMITTER:tsuyoshi wada mail@example.com 1514808000@@__CHGLOG_DELIMITER__@@SUBJECT:[JIRA-1111]: Add new feature #123@@__CHGLOG_DELIMITER__@@BODY: This is body message.
|
||||
48
testdata/trimmed_body.md
vendored
Normal file
48
testdata/trimmed_body.md
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
{{ if .Versions -}}
|
||||
<a name="unreleased"></a>
|
||||
## [Unreleased]
|
||||
|
||||
{{ if .Unreleased.CommitGroups -}}
|
||||
{{ range .Unreleased.CommitGroups -}}
|
||||
### {{ .Title }}
|
||||
{{ range .Commits -}}
|
||||
- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
|
||||
{{ if .TrimmedBody -}}
|
||||
{{ indent .TrimmedBody 2 }}
|
||||
{{ end -}}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
|
||||
{{ range .Versions }}
|
||||
<a name="{{ .Tag.Name }}"></a>
|
||||
## {{ if .Tag.Previous }}[{{ .Tag.Name }}]{{ else }}{{ .Tag.Name }}{{ end }} - {{ datetime "2006-01-02" .Tag.Date }}
|
||||
{{ range .CommitGroups -}}
|
||||
### {{ .Title }}
|
||||
{{ range .Commits -}}
|
||||
- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
|
||||
{{ if .TrimmedBody -}}
|
||||
{{ indent .TrimmedBody 2 }}
|
||||
{{ end -}}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .NoteGroups -}}
|
||||
{{ range .NoteGroups -}}
|
||||
### {{ .Title }}
|
||||
{{ range .Notes }}
|
||||
{{ .Body }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .Versions }}
|
||||
[Unreleased]: {{ .Info.RepositoryURL }}/compare/{{ $latest := index .Versions 0 }}{{ $latest.Tag.Name }}...HEAD
|
||||
{{ range .Versions -}}
|
||||
{{ if .Tag.Previous -}}
|
||||
[{{ .Tag.Name }}]: {{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
74
testdata/type_scope_subject.md
vendored
74
testdata/type_scope_subject.md
vendored
|
|
@ -1,22 +1,56 @@
|
|||
{{range .Versions}}
|
||||
<a name="{{urlquery .Tag.Name}}"></a>
|
||||
## {{.Tag.Name}} ({{datetime "2006-01-02" .Tag.Date}})
|
||||
{{range .CommitGroups}}
|
||||
### {{.Title}}
|
||||
{{range .Commits}}
|
||||
* {{if ne .Scope ""}}**{{.Scope}}:** {{end}}{{.Subject}}{{end}}
|
||||
{{end}}{{if .RevertCommits}}
|
||||
{{ if .Versions -}}
|
||||
<a name="unreleased"></a>
|
||||
## [Unreleased]
|
||||
|
||||
{{ if .Unreleased.CommitGroups -}}
|
||||
{{ range .Unreleased.CommitGroups -}}
|
||||
### {{ .Title }}
|
||||
{{ range .Commits -}}
|
||||
- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
|
||||
{{ range .Versions }}
|
||||
<a name="{{ .Tag.Name }}"></a>
|
||||
## {{ if .Tag.Previous }}[{{ .Tag.Name }}]{{ else }}{{ .Tag.Name }}{{ end }} - {{ datetime "2006-01-02" .Tag.Date }}
|
||||
{{ range .CommitGroups -}}
|
||||
### {{ .Title }}
|
||||
{{ range .Commits -}}
|
||||
- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .RevertCommits -}}
|
||||
### Reverts
|
||||
{{range .RevertCommits}}
|
||||
* {{.Revert.Header}}{{end}}
|
||||
{{end}}{{if .MergeCommits}}
|
||||
{{ range .RevertCommits -}}
|
||||
- {{ .Revert.Header }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .MergeCommits -}}
|
||||
### Pull Requests
|
||||
{{range .MergeCommits}}
|
||||
* {{.Header}}{{end}}
|
||||
{{end}}{{range .NoteGroups}}
|
||||
### {{.Title}}
|
||||
{{range .Notes}}
|
||||
{{.Body}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{ range .MergeCommits -}}
|
||||
- {{ .Header }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .NoteGroups -}}
|
||||
{{ range .NoteGroups -}}
|
||||
### {{ .Title }}
|
||||
{{ range .Notes }}
|
||||
{{ .Body }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .Versions }}
|
||||
[Unreleased]: {{ .Info.RepositoryURL }}/compare/{{ $latest := index .Versions 0 }}{{ $latest.Tag.Name }}...HEAD
|
||||
{{ range .Versions -}}
|
||||
{{ if .Tag.Previous -}}
|
||||
[{{ .Tag.Name }}]: {{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
|
|
|
|||
57
testdata/with_sprig.md
vendored
Normal file
57
testdata/with_sprig.md
vendored
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
{{ title "my changelog" }}
|
||||
{{ if .Versions -}}
|
||||
<a name="unreleased"></a>
|
||||
## [Unreleased]
|
||||
|
||||
{{ if .Unreleased.CommitGroups -}}
|
||||
{{ range .Unreleased.CommitGroups -}}
|
||||
### {{ .Title }}
|
||||
{{ range .Commits -}}
|
||||
- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
|
||||
{{ range .Versions }}
|
||||
<a name="{{ .Tag.Name }}"></a>
|
||||
## {{ if .Tag.Previous }}[{{ .Tag.Name }}]{{ else }}{{ .Tag.Name }}{{ end }} - {{ datetime "2006-01-02" .Tag.Date }}
|
||||
{{ range .CommitGroups -}}
|
||||
### {{ .Title }}
|
||||
{{ range .Commits -}}
|
||||
- {{ if .Scope }}**{{ .Scope | upper }}:** {{ end }}{{ .Subject }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .RevertCommits -}}
|
||||
### Reverts
|
||||
{{ range .RevertCommits -}}
|
||||
- {{ .Revert.Header }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .MergeCommits -}}
|
||||
### Pull Requests
|
||||
{{ range .MergeCommits -}}
|
||||
- {{ .Header }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .NoteGroups -}}
|
||||
{{ range .NoteGroups -}}
|
||||
### {{ .Title }}
|
||||
{{ range .Notes }}
|
||||
{{ .Body }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .Versions }}
|
||||
[Unreleased]: {{ .Info.RepositoryURL }}/compare/{{ $latest := index .Versions 0 }}{{ $latest.Tag.Name }}...HEAD
|
||||
{{ range .Versions -}}
|
||||
{{ if .Tag.Previous -}}
|
||||
[{{ .Tag.Name }}]: {{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
1
utils.go
1
utils.go
|
|
@ -23,6 +23,7 @@ func dotGet(target interface{}, prop string) (interface{}, bool) {
|
|||
value = reflect.ValueOf(target)
|
||||
}
|
||||
|
||||
//nolint:staticcheck
|
||||
field := value.FieldByName(strings.Title(key))
|
||||
if !field.IsValid() {
|
||||
return nil, false
|
||||
|
|
|
|||
15
vendor/github.com/davecgh/go-spew/LICENSE
generated
vendored
15
vendor/github.com/davecgh/go-spew/LICENSE
generated
vendored
|
|
@ -1,15 +0,0 @@
|
|||
ISC License
|
||||
|
||||
Copyright (c) 2012-2016 Dave Collins <dave@davec.name>
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
152
vendor/github.com/davecgh/go-spew/spew/bypass.go
generated
vendored
152
vendor/github.com/davecgh/go-spew/spew/bypass.go
generated
vendored
|
|
@ -1,152 +0,0 @@
|
|||
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
||||
//
|
||||
// Permission to use, copy, modify, and distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||
// when the code is not running on Google App Engine, compiled by GopherJS, and
|
||||
// "-tags safe" is not added to the go build command line. The "disableunsafe"
|
||||
// tag is deprecated and thus should not be used.
|
||||
// +build !js,!appengine,!safe,!disableunsafe
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// UnsafeDisabled is a build-time constant which specifies whether or
|
||||
// not access to the unsafe package is available.
|
||||
UnsafeDisabled = false
|
||||
|
||||
// ptrSize is the size of a pointer on the current arch.
|
||||
ptrSize = unsafe.Sizeof((*byte)(nil))
|
||||
)
|
||||
|
||||
var (
|
||||
// offsetPtr, offsetScalar, and offsetFlag are the offsets for the
|
||||
// internal reflect.Value fields. These values are valid before golang
|
||||
// commit ecccf07e7f9d which changed the format. The are also valid
|
||||
// after commit 82f48826c6c7 which changed the format again to mirror
|
||||
// the original format. Code in the init function updates these offsets
|
||||
// as necessary.
|
||||
offsetPtr = uintptr(ptrSize)
|
||||
offsetScalar = uintptr(0)
|
||||
offsetFlag = uintptr(ptrSize * 2)
|
||||
|
||||
// flagKindWidth and flagKindShift indicate various bits that the
|
||||
// reflect package uses internally to track kind information.
|
||||
//
|
||||
// flagRO indicates whether or not the value field of a reflect.Value is
|
||||
// read-only.
|
||||
//
|
||||
// flagIndir indicates whether the value field of a reflect.Value is
|
||||
// the actual data or a pointer to the data.
|
||||
//
|
||||
// These values are valid before golang commit 90a7c3c86944 which
|
||||
// changed their positions. Code in the init function updates these
|
||||
// flags as necessary.
|
||||
flagKindWidth = uintptr(5)
|
||||
flagKindShift = uintptr(flagKindWidth - 1)
|
||||
flagRO = uintptr(1 << 0)
|
||||
flagIndir = uintptr(1 << 1)
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Older versions of reflect.Value stored small integers directly in the
|
||||
// ptr field (which is named val in the older versions). Versions
|
||||
// between commits ecccf07e7f9d and 82f48826c6c7 added a new field named
|
||||
// scalar for this purpose which unfortunately came before the flag
|
||||
// field, so the offset of the flag field is different for those
|
||||
// versions.
|
||||
//
|
||||
// This code constructs a new reflect.Value from a known small integer
|
||||
// and checks if the size of the reflect.Value struct indicates it has
|
||||
// the scalar field. When it does, the offsets are updated accordingly.
|
||||
vv := reflect.ValueOf(0xf00)
|
||||
if unsafe.Sizeof(vv) == (ptrSize * 4) {
|
||||
offsetScalar = ptrSize * 2
|
||||
offsetFlag = ptrSize * 3
|
||||
}
|
||||
|
||||
// Commit 90a7c3c86944 changed the flag positions such that the low
|
||||
// order bits are the kind. This code extracts the kind from the flags
|
||||
// field and ensures it's the correct type. When it's not, the flag
|
||||
// order has been changed to the newer format, so the flags are updated
|
||||
// accordingly.
|
||||
upf := unsafe.Pointer(uintptr(unsafe.Pointer(&vv)) + offsetFlag)
|
||||
upfv := *(*uintptr)(upf)
|
||||
flagKindMask := uintptr((1<<flagKindWidth - 1) << flagKindShift)
|
||||
if (upfv&flagKindMask)>>flagKindShift != uintptr(reflect.Int) {
|
||||
flagKindShift = 0
|
||||
flagRO = 1 << 5
|
||||
flagIndir = 1 << 6
|
||||
|
||||
// Commit adf9b30e5594 modified the flags to separate the
|
||||
// flagRO flag into two bits which specifies whether or not the
|
||||
// field is embedded. This causes flagIndir to move over a bit
|
||||
// and means that flagRO is the combination of either of the
|
||||
// original flagRO bit and the new bit.
|
||||
//
|
||||
// This code detects the change by extracting what used to be
|
||||
// the indirect bit to ensure it's set. When it's not, the flag
|
||||
// order has been changed to the newer format, so the flags are
|
||||
// updated accordingly.
|
||||
if upfv&flagIndir == 0 {
|
||||
flagRO = 3 << 5
|
||||
flagIndir = 1 << 7
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// unsafeReflectValue converts the passed reflect.Value into a one that bypasses
|
||||
// the typical safety restrictions preventing access to unaddressable and
|
||||
// unexported data. It works by digging the raw pointer to the underlying
|
||||
// value out of the protected value and generating a new unprotected (unsafe)
|
||||
// reflect.Value to it.
|
||||
//
|
||||
// This allows us to check for implementations of the Stringer and error
|
||||
// interfaces to be used for pretty printing ordinarily unaddressable and
|
||||
// inaccessible values such as unexported struct fields.
|
||||
func unsafeReflectValue(v reflect.Value) (rv reflect.Value) {
|
||||
indirects := 1
|
||||
vt := v.Type()
|
||||
upv := unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetPtr)
|
||||
rvf := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetFlag))
|
||||
if rvf&flagIndir != 0 {
|
||||
vt = reflect.PtrTo(v.Type())
|
||||
indirects++
|
||||
} else if offsetScalar != 0 {
|
||||
// The value is in the scalar field when it's not one of the
|
||||
// reference types.
|
||||
switch vt.Kind() {
|
||||
case reflect.Uintptr:
|
||||
case reflect.Chan:
|
||||
case reflect.Func:
|
||||
case reflect.Map:
|
||||
case reflect.Ptr:
|
||||
case reflect.UnsafePointer:
|
||||
default:
|
||||
upv = unsafe.Pointer(uintptr(unsafe.Pointer(&v)) +
|
||||
offsetScalar)
|
||||
}
|
||||
}
|
||||
|
||||
pv := reflect.NewAt(vt, upv)
|
||||
rv = pv
|
||||
for i := 0; i < indirects; i++ {
|
||||
rv = rv.Elem()
|
||||
}
|
||||
return rv
|
||||
}
|
||||
38
vendor/github.com/davecgh/go-spew/spew/bypasssafe.go
generated
vendored
38
vendor/github.com/davecgh/go-spew/spew/bypasssafe.go
generated
vendored
|
|
@ -1,38 +0,0 @@
|
|||
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
||||
//
|
||||
// Permission to use, copy, modify, and distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||
// when the code is running on Google App Engine, compiled by GopherJS, or
|
||||
// "-tags safe" is added to the go build command line. The "disableunsafe"
|
||||
// tag is deprecated and thus should not be used.
|
||||
// +build js appengine safe disableunsafe
|
||||
|
||||
package spew
|
||||
|
||||
import "reflect"
|
||||
|
||||
const (
|
||||
// UnsafeDisabled is a build-time constant which specifies whether or
|
||||
// not access to the unsafe package is available.
|
||||
UnsafeDisabled = true
|
||||
)
|
||||
|
||||
// unsafeReflectValue typically converts the passed reflect.Value into a one
|
||||
// that bypasses the typical safety restrictions preventing access to
|
||||
// unaddressable and unexported data. However, doing this relies on access to
|
||||
// the unsafe package. This is a stub version which simply returns the passed
|
||||
// reflect.Value when the unsafe package is not available.
|
||||
func unsafeReflectValue(v reflect.Value) reflect.Value {
|
||||
return v
|
||||
}
|
||||
341
vendor/github.com/davecgh/go-spew/spew/common.go
generated
vendored
341
vendor/github.com/davecgh/go-spew/spew/common.go
generated
vendored
|
|
@ -1,341 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Some constants in the form of bytes to avoid string overhead. This mirrors
|
||||
// the technique used in the fmt package.
|
||||
var (
|
||||
panicBytes = []byte("(PANIC=")
|
||||
plusBytes = []byte("+")
|
||||
iBytes = []byte("i")
|
||||
trueBytes = []byte("true")
|
||||
falseBytes = []byte("false")
|
||||
interfaceBytes = []byte("(interface {})")
|
||||
commaNewlineBytes = []byte(",\n")
|
||||
newlineBytes = []byte("\n")
|
||||
openBraceBytes = []byte("{")
|
||||
openBraceNewlineBytes = []byte("{\n")
|
||||
closeBraceBytes = []byte("}")
|
||||
asteriskBytes = []byte("*")
|
||||
colonBytes = []byte(":")
|
||||
colonSpaceBytes = []byte(": ")
|
||||
openParenBytes = []byte("(")
|
||||
closeParenBytes = []byte(")")
|
||||
spaceBytes = []byte(" ")
|
||||
pointerChainBytes = []byte("->")
|
||||
nilAngleBytes = []byte("<nil>")
|
||||
maxNewlineBytes = []byte("<max depth reached>\n")
|
||||
maxShortBytes = []byte("<max>")
|
||||
circularBytes = []byte("<already shown>")
|
||||
circularShortBytes = []byte("<shown>")
|
||||
invalidAngleBytes = []byte("<invalid>")
|
||||
openBracketBytes = []byte("[")
|
||||
closeBracketBytes = []byte("]")
|
||||
percentBytes = []byte("%")
|
||||
precisionBytes = []byte(".")
|
||||
openAngleBytes = []byte("<")
|
||||
closeAngleBytes = []byte(">")
|
||||
openMapBytes = []byte("map[")
|
||||
closeMapBytes = []byte("]")
|
||||
lenEqualsBytes = []byte("len=")
|
||||
capEqualsBytes = []byte("cap=")
|
||||
)
|
||||
|
||||
// hexDigits is used to map a decimal value to a hex digit.
|
||||
var hexDigits = "0123456789abcdef"
|
||||
|
||||
// catchPanic handles any panics that might occur during the handleMethods
|
||||
// calls.
|
||||
func catchPanic(w io.Writer, v reflect.Value) {
|
||||
if err := recover(); err != nil {
|
||||
w.Write(panicBytes)
|
||||
fmt.Fprintf(w, "%v", err)
|
||||
w.Write(closeParenBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// handleMethods attempts to call the Error and String methods on the underlying
|
||||
// type the passed reflect.Value represents and outputes the result to Writer w.
|
||||
//
|
||||
// It handles panics in any called methods by catching and displaying the error
|
||||
// as the formatted value.
|
||||
func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) {
|
||||
// We need an interface to check if the type implements the error or
|
||||
// Stringer interface. However, the reflect package won't give us an
|
||||
// interface on certain things like unexported struct fields in order
|
||||
// to enforce visibility rules. We use unsafe, when it's available,
|
||||
// to bypass these restrictions since this package does not mutate the
|
||||
// values.
|
||||
if !v.CanInterface() {
|
||||
if UnsafeDisabled {
|
||||
return false
|
||||
}
|
||||
|
||||
v = unsafeReflectValue(v)
|
||||
}
|
||||
|
||||
// Choose whether or not to do error and Stringer interface lookups against
|
||||
// the base type or a pointer to the base type depending on settings.
|
||||
// Technically calling one of these methods with a pointer receiver can
|
||||
// mutate the value, however, types which choose to satisify an error or
|
||||
// Stringer interface with a pointer receiver should not be mutating their
|
||||
// state inside these interface methods.
|
||||
if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() {
|
||||
v = unsafeReflectValue(v)
|
||||
}
|
||||
if v.CanAddr() {
|
||||
v = v.Addr()
|
||||
}
|
||||
|
||||
// Is it an error or Stringer?
|
||||
switch iface := v.Interface().(type) {
|
||||
case error:
|
||||
defer catchPanic(w, v)
|
||||
if cs.ContinueOnMethod {
|
||||
w.Write(openParenBytes)
|
||||
w.Write([]byte(iface.Error()))
|
||||
w.Write(closeParenBytes)
|
||||
w.Write(spaceBytes)
|
||||
return false
|
||||
}
|
||||
|
||||
w.Write([]byte(iface.Error()))
|
||||
return true
|
||||
|
||||
case fmt.Stringer:
|
||||
defer catchPanic(w, v)
|
||||
if cs.ContinueOnMethod {
|
||||
w.Write(openParenBytes)
|
||||
w.Write([]byte(iface.String()))
|
||||
w.Write(closeParenBytes)
|
||||
w.Write(spaceBytes)
|
||||
return false
|
||||
}
|
||||
w.Write([]byte(iface.String()))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// printBool outputs a boolean value as true or false to Writer w.
|
||||
func printBool(w io.Writer, val bool) {
|
||||
if val {
|
||||
w.Write(trueBytes)
|
||||
} else {
|
||||
w.Write(falseBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// printInt outputs a signed integer value to Writer w.
|
||||
func printInt(w io.Writer, val int64, base int) {
|
||||
w.Write([]byte(strconv.FormatInt(val, base)))
|
||||
}
|
||||
|
||||
// printUint outputs an unsigned integer value to Writer w.
|
||||
func printUint(w io.Writer, val uint64, base int) {
|
||||
w.Write([]byte(strconv.FormatUint(val, base)))
|
||||
}
|
||||
|
||||
// printFloat outputs a floating point value using the specified precision,
|
||||
// which is expected to be 32 or 64bit, to Writer w.
|
||||
func printFloat(w io.Writer, val float64, precision int) {
|
||||
w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision)))
|
||||
}
|
||||
|
||||
// printComplex outputs a complex value using the specified float precision
|
||||
// for the real and imaginary parts to Writer w.
|
||||
func printComplex(w io.Writer, c complex128, floatPrecision int) {
|
||||
r := real(c)
|
||||
w.Write(openParenBytes)
|
||||
w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision)))
|
||||
i := imag(c)
|
||||
if i >= 0 {
|
||||
w.Write(plusBytes)
|
||||
}
|
||||
w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision)))
|
||||
w.Write(iBytes)
|
||||
w.Write(closeParenBytes)
|
||||
}
|
||||
|
||||
// printHexPtr outputs a uintptr formatted as hexidecimal with a leading '0x'
|
||||
// prefix to Writer w.
|
||||
func printHexPtr(w io.Writer, p uintptr) {
|
||||
// Null pointer.
|
||||
num := uint64(p)
|
||||
if num == 0 {
|
||||
w.Write(nilAngleBytes)
|
||||
return
|
||||
}
|
||||
|
||||
// Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix
|
||||
buf := make([]byte, 18)
|
||||
|
||||
// It's simpler to construct the hex string right to left.
|
||||
base := uint64(16)
|
||||
i := len(buf) - 1
|
||||
for num >= base {
|
||||
buf[i] = hexDigits[num%base]
|
||||
num /= base
|
||||
i--
|
||||
}
|
||||
buf[i] = hexDigits[num]
|
||||
|
||||
// Add '0x' prefix.
|
||||
i--
|
||||
buf[i] = 'x'
|
||||
i--
|
||||
buf[i] = '0'
|
||||
|
||||
// Strip unused leading bytes.
|
||||
buf = buf[i:]
|
||||
w.Write(buf)
|
||||
}
|
||||
|
||||
// valuesSorter implements sort.Interface to allow a slice of reflect.Value
|
||||
// elements to be sorted.
|
||||
type valuesSorter struct {
|
||||
values []reflect.Value
|
||||
strings []string // either nil or same len and values
|
||||
cs *ConfigState
|
||||
}
|
||||
|
||||
// newValuesSorter initializes a valuesSorter instance, which holds a set of
|
||||
// surrogate keys on which the data should be sorted. It uses flags in
|
||||
// ConfigState to decide if and how to populate those surrogate keys.
|
||||
func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface {
|
||||
vs := &valuesSorter{values: values, cs: cs}
|
||||
if canSortSimply(vs.values[0].Kind()) {
|
||||
return vs
|
||||
}
|
||||
if !cs.DisableMethods {
|
||||
vs.strings = make([]string, len(values))
|
||||
for i := range vs.values {
|
||||
b := bytes.Buffer{}
|
||||
if !handleMethods(cs, &b, vs.values[i]) {
|
||||
vs.strings = nil
|
||||
break
|
||||
}
|
||||
vs.strings[i] = b.String()
|
||||
}
|
||||
}
|
||||
if vs.strings == nil && cs.SpewKeys {
|
||||
vs.strings = make([]string, len(values))
|
||||
for i := range vs.values {
|
||||
vs.strings[i] = Sprintf("%#v", vs.values[i].Interface())
|
||||
}
|
||||
}
|
||||
return vs
|
||||
}
|
||||
|
||||
// canSortSimply tests whether a reflect.Kind is a primitive that can be sorted
|
||||
// directly, or whether it should be considered for sorting by surrogate keys
|
||||
// (if the ConfigState allows it).
|
||||
func canSortSimply(kind reflect.Kind) bool {
|
||||
// This switch parallels valueSortLess, except for the default case.
|
||||
switch kind {
|
||||
case reflect.Bool:
|
||||
return true
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||
return true
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||
return true
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return true
|
||||
case reflect.String:
|
||||
return true
|
||||
case reflect.Uintptr:
|
||||
return true
|
||||
case reflect.Array:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Len returns the number of values in the slice. It is part of the
|
||||
// sort.Interface implementation.
|
||||
func (s *valuesSorter) Len() int {
|
||||
return len(s.values)
|
||||
}
|
||||
|
||||
// Swap swaps the values at the passed indices. It is part of the
|
||||
// sort.Interface implementation.
|
||||
func (s *valuesSorter) Swap(i, j int) {
|
||||
s.values[i], s.values[j] = s.values[j], s.values[i]
|
||||
if s.strings != nil {
|
||||
s.strings[i], s.strings[j] = s.strings[j], s.strings[i]
|
||||
}
|
||||
}
|
||||
|
||||
// valueSortLess returns whether the first value should sort before the second
|
||||
// value. It is used by valueSorter.Less as part of the sort.Interface
|
||||
// implementation.
|
||||
func valueSortLess(a, b reflect.Value) bool {
|
||||
switch a.Kind() {
|
||||
case reflect.Bool:
|
||||
return !a.Bool() && b.Bool()
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||
return a.Int() < b.Int()
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||
return a.Uint() < b.Uint()
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return a.Float() < b.Float()
|
||||
case reflect.String:
|
||||
return a.String() < b.String()
|
||||
case reflect.Uintptr:
|
||||
return a.Uint() < b.Uint()
|
||||
case reflect.Array:
|
||||
// Compare the contents of both arrays.
|
||||
l := a.Len()
|
||||
for i := 0; i < l; i++ {
|
||||
av := a.Index(i)
|
||||
bv := b.Index(i)
|
||||
if av.Interface() == bv.Interface() {
|
||||
continue
|
||||
}
|
||||
return valueSortLess(av, bv)
|
||||
}
|
||||
}
|
||||
return a.String() < b.String()
|
||||
}
|
||||
|
||||
// Less returns whether the value at index i should sort before the
|
||||
// value at index j. It is part of the sort.Interface implementation.
|
||||
func (s *valuesSorter) Less(i, j int) bool {
|
||||
if s.strings == nil {
|
||||
return valueSortLess(s.values[i], s.values[j])
|
||||
}
|
||||
return s.strings[i] < s.strings[j]
|
||||
}
|
||||
|
||||
// sortValues is a sort function that handles both native types and any type that
|
||||
// can be converted to error or Stringer. Other inputs are sorted according to
|
||||
// their Value.String() value to ensure display stability.
|
||||
func sortValues(values []reflect.Value, cs *ConfigState) {
|
||||
if len(values) == 0 {
|
||||
return
|
||||
}
|
||||
sort.Sort(newValuesSorter(values, cs))
|
||||
}
|
||||
306
vendor/github.com/davecgh/go-spew/spew/config.go
generated
vendored
306
vendor/github.com/davecgh/go-spew/spew/config.go
generated
vendored
|
|
@ -1,306 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// ConfigState houses the configuration options used by spew to format and
|
||||
// display values. There is a global instance, Config, that is used to control
|
||||
// all top-level Formatter and Dump functionality. Each ConfigState instance
|
||||
// provides methods equivalent to the top-level functions.
|
||||
//
|
||||
// The zero value for ConfigState provides no indentation. You would typically
|
||||
// want to set it to a space or a tab.
|
||||
//
|
||||
// Alternatively, you can use NewDefaultConfig to get a ConfigState instance
|
||||
// with default settings. See the documentation of NewDefaultConfig for default
|
||||
// values.
|
||||
type ConfigState struct {
|
||||
// Indent specifies the string to use for each indentation level. The
|
||||
// global config instance that all top-level functions use set this to a
|
||||
// single space by default. If you would like more indentation, you might
|
||||
// set this to a tab with "\t" or perhaps two spaces with " ".
|
||||
Indent string
|
||||
|
||||
// MaxDepth controls the maximum number of levels to descend into nested
|
||||
// data structures. The default, 0, means there is no limit.
|
||||
//
|
||||
// NOTE: Circular data structures are properly detected, so it is not
|
||||
// necessary to set this value unless you specifically want to limit deeply
|
||||
// nested data structures.
|
||||
MaxDepth int
|
||||
|
||||
// DisableMethods specifies whether or not error and Stringer interfaces are
|
||||
// invoked for types that implement them.
|
||||
DisableMethods bool
|
||||
|
||||
// DisablePointerMethods specifies whether or not to check for and invoke
|
||||
// error and Stringer interfaces on types which only accept a pointer
|
||||
// receiver when the current type is not a pointer.
|
||||
//
|
||||
// NOTE: This might be an unsafe action since calling one of these methods
|
||||
// with a pointer receiver could technically mutate the value, however,
|
||||
// in practice, types which choose to satisify an error or Stringer
|
||||
// interface with a pointer receiver should not be mutating their state
|
||||
// inside these interface methods. As a result, this option relies on
|
||||
// access to the unsafe package, so it will not have any effect when
|
||||
// running in environments without access to the unsafe package such as
|
||||
// Google App Engine or with the "safe" build tag specified.
|
||||
DisablePointerMethods bool
|
||||
|
||||
// DisablePointerAddresses specifies whether to disable the printing of
|
||||
// pointer addresses. This is useful when diffing data structures in tests.
|
||||
DisablePointerAddresses bool
|
||||
|
||||
// DisableCapacities specifies whether to disable the printing of capacities
|
||||
// for arrays, slices, maps and channels. This is useful when diffing
|
||||
// data structures in tests.
|
||||
DisableCapacities bool
|
||||
|
||||
// ContinueOnMethod specifies whether or not recursion should continue once
|
||||
// a custom error or Stringer interface is invoked. The default, false,
|
||||
// means it will print the results of invoking the custom error or Stringer
|
||||
// interface and return immediately instead of continuing to recurse into
|
||||
// the internals of the data type.
|
||||
//
|
||||
// NOTE: This flag does not have any effect if method invocation is disabled
|
||||
// via the DisableMethods or DisablePointerMethods options.
|
||||
ContinueOnMethod bool
|
||||
|
||||
// SortKeys specifies map keys should be sorted before being printed. Use
|
||||
// this to have a more deterministic, diffable output. Note that only
|
||||
// native types (bool, int, uint, floats, uintptr and string) and types
|
||||
// that support the error or Stringer interfaces (if methods are
|
||||
// enabled) are supported, with other types sorted according to the
|
||||
// reflect.Value.String() output which guarantees display stability.
|
||||
SortKeys bool
|
||||
|
||||
// SpewKeys specifies that, as a last resort attempt, map keys should
|
||||
// be spewed to strings and sorted by those strings. This is only
|
||||
// considered if SortKeys is true.
|
||||
SpewKeys bool
|
||||
}
|
||||
|
||||
// Config is the active configuration of the top-level functions.
|
||||
// The configuration can be changed by modifying the contents of spew.Config.
|
||||
var Config = ConfigState{Indent: " "}
|
||||
|
||||
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the formatted string as a value that satisfies error. See NewFormatter
|
||||
// for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Errorf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Errorf(format string, a ...interface{}) (err error) {
|
||||
return fmt.Errorf(format, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprint(w, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprint(w, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprintf(w, format, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintf(w, format, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
|
||||
// passed with a Formatter interface returned by c.NewFormatter. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprintln(w, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintln(w, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Print is a wrapper for fmt.Print that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Print(c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Print(a ...interface{}) (n int, err error) {
|
||||
return fmt.Print(c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Printf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Printf(format string, a ...interface{}) (n int, err error) {
|
||||
return fmt.Printf(format, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Println is a wrapper for fmt.Println that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Println(c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Println(a ...interface{}) (n int, err error) {
|
||||
return fmt.Println(c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprint(c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Sprint(a ...interface{}) string {
|
||||
return fmt.Sprint(c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprintf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Sprintf(format string, a ...interface{}) string {
|
||||
return fmt.Sprintf(format, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
|
||||
// were passed with a Formatter interface returned by c.NewFormatter. It
|
||||
// returns the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprintln(c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Sprintln(a ...interface{}) string {
|
||||
return fmt.Sprintln(c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
/*
|
||||
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
|
||||
interface. As a result, it integrates cleanly with standard fmt package
|
||||
printing functions. The formatter is useful for inline printing of smaller data
|
||||
types similar to the standard %v format specifier.
|
||||
|
||||
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||
addresses), %#v (adds types), and %#+v (adds types and pointer addresses) verb
|
||||
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||
the width and precision arguments (however they will still work on the format
|
||||
specifiers not handled by the custom formatter).
|
||||
|
||||
Typically this function shouldn't be called directly. It is much easier to make
|
||||
use of the custom formatter by calling one of the convenience functions such as
|
||||
c.Printf, c.Println, or c.Printf.
|
||||
*/
|
||||
func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter {
|
||||
return newFormatter(c, v)
|
||||
}
|
||||
|
||||
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
||||
// exactly the same as Dump.
|
||||
func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) {
|
||||
fdump(c, w, a...)
|
||||
}
|
||||
|
||||
/*
|
||||
Dump displays the passed parameters to standard out with newlines, customizable
|
||||
indentation, and additional debug information such as complete types and all
|
||||
pointer addresses used to indirect to the final value. It provides the
|
||||
following features over the built-in printing facilities provided by the fmt
|
||||
package:
|
||||
|
||||
* Pointers are dereferenced and followed
|
||||
* Circular data structures are detected and handled properly
|
||||
* Custom Stringer/error interfaces are optionally invoked, including
|
||||
on unexported types
|
||||
* Custom types which only implement the Stringer/error interfaces via
|
||||
a pointer receiver are optionally invoked when passing non-pointer
|
||||
variables
|
||||
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||
includes offsets, byte values in hex, and ASCII output
|
||||
|
||||
The configuration options are controlled by modifying the public members
|
||||
of c. See ConfigState for options documentation.
|
||||
|
||||
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
||||
get the formatted result as a string.
|
||||
*/
|
||||
func (c *ConfigState) Dump(a ...interface{}) {
|
||||
fdump(c, os.Stdout, a...)
|
||||
}
|
||||
|
||||
// Sdump returns a string with the passed arguments formatted exactly the same
|
||||
// as Dump.
|
||||
func (c *ConfigState) Sdump(a ...interface{}) string {
|
||||
var buf bytes.Buffer
|
||||
fdump(c, &buf, a...)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// convertArgs accepts a slice of arguments and returns a slice of the same
|
||||
// length with each argument converted to a spew Formatter interface using
|
||||
// the ConfigState associated with s.
|
||||
func (c *ConfigState) convertArgs(args []interface{}) (formatters []interface{}) {
|
||||
formatters = make([]interface{}, len(args))
|
||||
for index, arg := range args {
|
||||
formatters[index] = newFormatter(c, arg)
|
||||
}
|
||||
return formatters
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a ConfigState with the following default settings.
|
||||
//
|
||||
// Indent: " "
|
||||
// MaxDepth: 0
|
||||
// DisableMethods: false
|
||||
// DisablePointerMethods: false
|
||||
// ContinueOnMethod: false
|
||||
// SortKeys: false
|
||||
func NewDefaultConfig() *ConfigState {
|
||||
return &ConfigState{Indent: " "}
|
||||
}
|
||||
211
vendor/github.com/davecgh/go-spew/spew/doc.go
generated
vendored
211
vendor/github.com/davecgh/go-spew/spew/doc.go
generated
vendored
|
|
@ -1,211 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
Package spew implements a deep pretty printer for Go data structures to aid in
|
||||
debugging.
|
||||
|
||||
A quick overview of the additional features spew provides over the built-in
|
||||
printing facilities for Go data types are as follows:
|
||||
|
||||
* Pointers are dereferenced and followed
|
||||
* Circular data structures are detected and handled properly
|
||||
* Custom Stringer/error interfaces are optionally invoked, including
|
||||
on unexported types
|
||||
* Custom types which only implement the Stringer/error interfaces via
|
||||
a pointer receiver are optionally invoked when passing non-pointer
|
||||
variables
|
||||
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||
includes offsets, byte values in hex, and ASCII output (only when using
|
||||
Dump style)
|
||||
|
||||
There are two different approaches spew allows for dumping Go data structures:
|
||||
|
||||
* Dump style which prints with newlines, customizable indentation,
|
||||
and additional debug information such as types and all pointer addresses
|
||||
used to indirect to the final value
|
||||
* A custom Formatter interface that integrates cleanly with the standard fmt
|
||||
package and replaces %v, %+v, %#v, and %#+v to provide inline printing
|
||||
similar to the default %v while providing the additional functionality
|
||||
outlined above and passing unsupported format verbs such as %x and %q
|
||||
along to fmt
|
||||
|
||||
Quick Start
|
||||
|
||||
This section demonstrates how to quickly get started with spew. See the
|
||||
sections below for further details on formatting and configuration options.
|
||||
|
||||
To dump a variable with full newlines, indentation, type, and pointer
|
||||
information use Dump, Fdump, or Sdump:
|
||||
spew.Dump(myVar1, myVar2, ...)
|
||||
spew.Fdump(someWriter, myVar1, myVar2, ...)
|
||||
str := spew.Sdump(myVar1, myVar2, ...)
|
||||
|
||||
Alternatively, if you would prefer to use format strings with a compacted inline
|
||||
printing style, use the convenience wrappers Printf, Fprintf, etc with
|
||||
%v (most compact), %+v (adds pointer addresses), %#v (adds types), or
|
||||
%#+v (adds types and pointer addresses):
|
||||
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||
|
||||
Configuration Options
|
||||
|
||||
Configuration of spew is handled by fields in the ConfigState type. For
|
||||
convenience, all of the top-level functions use a global state available
|
||||
via the spew.Config global.
|
||||
|
||||
It is also possible to create a ConfigState instance that provides methods
|
||||
equivalent to the top-level functions. This allows concurrent configuration
|
||||
options. See the ConfigState documentation for more details.
|
||||
|
||||
The following configuration options are available:
|
||||
* Indent
|
||||
String to use for each indentation level for Dump functions.
|
||||
It is a single space by default. A popular alternative is "\t".
|
||||
|
||||
* MaxDepth
|
||||
Maximum number of levels to descend into nested data structures.
|
||||
There is no limit by default.
|
||||
|
||||
* DisableMethods
|
||||
Disables invocation of error and Stringer interface methods.
|
||||
Method invocation is enabled by default.
|
||||
|
||||
* DisablePointerMethods
|
||||
Disables invocation of error and Stringer interface methods on types
|
||||
which only accept pointer receivers from non-pointer variables.
|
||||
Pointer method invocation is enabled by default.
|
||||
|
||||
* DisablePointerAddresses
|
||||
DisablePointerAddresses specifies whether to disable the printing of
|
||||
pointer addresses. This is useful when diffing data structures in tests.
|
||||
|
||||
* DisableCapacities
|
||||
DisableCapacities specifies whether to disable the printing of
|
||||
capacities for arrays, slices, maps and channels. This is useful when
|
||||
diffing data structures in tests.
|
||||
|
||||
* ContinueOnMethod
|
||||
Enables recursion into types after invoking error and Stringer interface
|
||||
methods. Recursion after method invocation is disabled by default.
|
||||
|
||||
* SortKeys
|
||||
Specifies map keys should be sorted before being printed. Use
|
||||
this to have a more deterministic, diffable output. Note that
|
||||
only native types (bool, int, uint, floats, uintptr and string)
|
||||
and types which implement error or Stringer interfaces are
|
||||
supported with other types sorted according to the
|
||||
reflect.Value.String() output which guarantees display
|
||||
stability. Natural map order is used by default.
|
||||
|
||||
* SpewKeys
|
||||
Specifies that, as a last resort attempt, map keys should be
|
||||
spewed to strings and sorted by those strings. This is only
|
||||
considered if SortKeys is true.
|
||||
|
||||
Dump Usage
|
||||
|
||||
Simply call spew.Dump with a list of variables you want to dump:
|
||||
|
||||
spew.Dump(myVar1, myVar2, ...)
|
||||
|
||||
You may also call spew.Fdump if you would prefer to output to an arbitrary
|
||||
io.Writer. For example, to dump to standard error:
|
||||
|
||||
spew.Fdump(os.Stderr, myVar1, myVar2, ...)
|
||||
|
||||
A third option is to call spew.Sdump to get the formatted output as a string:
|
||||
|
||||
str := spew.Sdump(myVar1, myVar2, ...)
|
||||
|
||||
Sample Dump Output
|
||||
|
||||
See the Dump example for details on the setup of the types and variables being
|
||||
shown here.
|
||||
|
||||
(main.Foo) {
|
||||
unexportedField: (*main.Bar)(0xf84002e210)({
|
||||
flag: (main.Flag) flagTwo,
|
||||
data: (uintptr) <nil>
|
||||
}),
|
||||
ExportedField: (map[interface {}]interface {}) (len=1) {
|
||||
(string) (len=3) "one": (bool) true
|
||||
}
|
||||
}
|
||||
|
||||
Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C
|
||||
command as shown.
|
||||
([]uint8) (len=32 cap=32) {
|
||||
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
|
||||
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
|
||||
00000020 31 32 |12|
|
||||
}
|
||||
|
||||
Custom Formatter
|
||||
|
||||
Spew provides a custom formatter that implements the fmt.Formatter interface
|
||||
so that it integrates cleanly with standard fmt package printing functions. The
|
||||
formatter is useful for inline printing of smaller data types similar to the
|
||||
standard %v format specifier.
|
||||
|
||||
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
|
||||
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||
the width and precision arguments (however they will still work on the format
|
||||
specifiers not handled by the custom formatter).
|
||||
|
||||
Custom Formatter Usage
|
||||
|
||||
The simplest way to make use of the spew custom formatter is to call one of the
|
||||
convenience functions such as spew.Printf, spew.Println, or spew.Printf. The
|
||||
functions have syntax you are most likely already familiar with:
|
||||
|
||||
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||
spew.Println(myVar, myVar2)
|
||||
spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||
spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||
|
||||
See the Index for the full list convenience functions.
|
||||
|
||||
Sample Formatter Output
|
||||
|
||||
Double pointer to a uint8:
|
||||
%v: <**>5
|
||||
%+v: <**>(0xf8400420d0->0xf8400420c8)5
|
||||
%#v: (**uint8)5
|
||||
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
|
||||
|
||||
Pointer to circular struct with a uint8 field and a pointer to itself:
|
||||
%v: <*>{1 <*><shown>}
|
||||
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
|
||||
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
|
||||
%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)<shown>}
|
||||
|
||||
See the Printf example for details on the setup of variables being shown
|
||||
here.
|
||||
|
||||
Errors
|
||||
|
||||
Since it is possible for custom Stringer/error interfaces to panic, spew
|
||||
detects them and handles them internally by printing the panic information
|
||||
inline with the output. Since spew is intended to provide deep pretty printing
|
||||
capabilities on structures, it intentionally does not return any errors.
|
||||
*/
|
||||
package spew
|
||||
509
vendor/github.com/davecgh/go-spew/spew/dump.go
generated
vendored
509
vendor/github.com/davecgh/go-spew/spew/dump.go
generated
vendored
|
|
@ -1,509 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// uint8Type is a reflect.Type representing a uint8. It is used to
|
||||
// convert cgo types to uint8 slices for hexdumping.
|
||||
uint8Type = reflect.TypeOf(uint8(0))
|
||||
|
||||
// cCharRE is a regular expression that matches a cgo char.
|
||||
// It is used to detect character arrays to hexdump them.
|
||||
cCharRE = regexp.MustCompile("^.*\\._Ctype_char$")
|
||||
|
||||
// cUnsignedCharRE is a regular expression that matches a cgo unsigned
|
||||
// char. It is used to detect unsigned character arrays to hexdump
|
||||
// them.
|
||||
cUnsignedCharRE = regexp.MustCompile("^.*\\._Ctype_unsignedchar$")
|
||||
|
||||
// cUint8tCharRE is a regular expression that matches a cgo uint8_t.
|
||||
// It is used to detect uint8_t arrays to hexdump them.
|
||||
cUint8tCharRE = regexp.MustCompile("^.*\\._Ctype_uint8_t$")
|
||||
)
|
||||
|
||||
// dumpState contains information about the state of a dump operation.
|
||||
type dumpState struct {
|
||||
w io.Writer
|
||||
depth int
|
||||
pointers map[uintptr]int
|
||||
ignoreNextType bool
|
||||
ignoreNextIndent bool
|
||||
cs *ConfigState
|
||||
}
|
||||
|
||||
// indent performs indentation according to the depth level and cs.Indent
|
||||
// option.
|
||||
func (d *dumpState) indent() {
|
||||
if d.ignoreNextIndent {
|
||||
d.ignoreNextIndent = false
|
||||
return
|
||||
}
|
||||
d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth))
|
||||
}
|
||||
|
||||
// unpackValue returns values inside of non-nil interfaces when possible.
|
||||
// This is useful for data types like structs, arrays, slices, and maps which
|
||||
// can contain varying types packed inside an interface.
|
||||
func (d *dumpState) unpackValue(v reflect.Value) reflect.Value {
|
||||
if v.Kind() == reflect.Interface && !v.IsNil() {
|
||||
v = v.Elem()
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// dumpPtr handles formatting of pointers by indirecting them as necessary.
|
||||
func (d *dumpState) dumpPtr(v reflect.Value) {
|
||||
// Remove pointers at or below the current depth from map used to detect
|
||||
// circular refs.
|
||||
for k, depth := range d.pointers {
|
||||
if depth >= d.depth {
|
||||
delete(d.pointers, k)
|
||||
}
|
||||
}
|
||||
|
||||
// Keep list of all dereferenced pointers to show later.
|
||||
pointerChain := make([]uintptr, 0)
|
||||
|
||||
// Figure out how many levels of indirection there are by dereferencing
|
||||
// pointers and unpacking interfaces down the chain while detecting circular
|
||||
// references.
|
||||
nilFound := false
|
||||
cycleFound := false
|
||||
indirects := 0
|
||||
ve := v
|
||||
for ve.Kind() == reflect.Ptr {
|
||||
if ve.IsNil() {
|
||||
nilFound = true
|
||||
break
|
||||
}
|
||||
indirects++
|
||||
addr := ve.Pointer()
|
||||
pointerChain = append(pointerChain, addr)
|
||||
if pd, ok := d.pointers[addr]; ok && pd < d.depth {
|
||||
cycleFound = true
|
||||
indirects--
|
||||
break
|
||||
}
|
||||
d.pointers[addr] = d.depth
|
||||
|
||||
ve = ve.Elem()
|
||||
if ve.Kind() == reflect.Interface {
|
||||
if ve.IsNil() {
|
||||
nilFound = true
|
||||
break
|
||||
}
|
||||
ve = ve.Elem()
|
||||
}
|
||||
}
|
||||
|
||||
// Display type information.
|
||||
d.w.Write(openParenBytes)
|
||||
d.w.Write(bytes.Repeat(asteriskBytes, indirects))
|
||||
d.w.Write([]byte(ve.Type().String()))
|
||||
d.w.Write(closeParenBytes)
|
||||
|
||||
// Display pointer information.
|
||||
if !d.cs.DisablePointerAddresses && len(pointerChain) > 0 {
|
||||
d.w.Write(openParenBytes)
|
||||
for i, addr := range pointerChain {
|
||||
if i > 0 {
|
||||
d.w.Write(pointerChainBytes)
|
||||
}
|
||||
printHexPtr(d.w, addr)
|
||||
}
|
||||
d.w.Write(closeParenBytes)
|
||||
}
|
||||
|
||||
// Display dereferenced value.
|
||||
d.w.Write(openParenBytes)
|
||||
switch {
|
||||
case nilFound == true:
|
||||
d.w.Write(nilAngleBytes)
|
||||
|
||||
case cycleFound == true:
|
||||
d.w.Write(circularBytes)
|
||||
|
||||
default:
|
||||
d.ignoreNextType = true
|
||||
d.dump(ve)
|
||||
}
|
||||
d.w.Write(closeParenBytes)
|
||||
}
|
||||
|
||||
// dumpSlice handles formatting of arrays and slices. Byte (uint8 under
|
||||
// reflection) arrays and slices are dumped in hexdump -C fashion.
|
||||
func (d *dumpState) dumpSlice(v reflect.Value) {
|
||||
// Determine whether this type should be hex dumped or not. Also,
|
||||
// for types which should be hexdumped, try to use the underlying data
|
||||
// first, then fall back to trying to convert them to a uint8 slice.
|
||||
var buf []uint8
|
||||
doConvert := false
|
||||
doHexDump := false
|
||||
numEntries := v.Len()
|
||||
if numEntries > 0 {
|
||||
vt := v.Index(0).Type()
|
||||
vts := vt.String()
|
||||
switch {
|
||||
// C types that need to be converted.
|
||||
case cCharRE.MatchString(vts):
|
||||
fallthrough
|
||||
case cUnsignedCharRE.MatchString(vts):
|
||||
fallthrough
|
||||
case cUint8tCharRE.MatchString(vts):
|
||||
doConvert = true
|
||||
|
||||
// Try to use existing uint8 slices and fall back to converting
|
||||
// and copying if that fails.
|
||||
case vt.Kind() == reflect.Uint8:
|
||||
// We need an addressable interface to convert the type
|
||||
// to a byte slice. However, the reflect package won't
|
||||
// give us an interface on certain things like
|
||||
// unexported struct fields in order to enforce
|
||||
// visibility rules. We use unsafe, when available, to
|
||||
// bypass these restrictions since this package does not
|
||||
// mutate the values.
|
||||
vs := v
|
||||
if !vs.CanInterface() || !vs.CanAddr() {
|
||||
vs = unsafeReflectValue(vs)
|
||||
}
|
||||
if !UnsafeDisabled {
|
||||
vs = vs.Slice(0, numEntries)
|
||||
|
||||
// Use the existing uint8 slice if it can be
|
||||
// type asserted.
|
||||
iface := vs.Interface()
|
||||
if slice, ok := iface.([]uint8); ok {
|
||||
buf = slice
|
||||
doHexDump = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// The underlying data needs to be converted if it can't
|
||||
// be type asserted to a uint8 slice.
|
||||
doConvert = true
|
||||
}
|
||||
|
||||
// Copy and convert the underlying type if needed.
|
||||
if doConvert && vt.ConvertibleTo(uint8Type) {
|
||||
// Convert and copy each element into a uint8 byte
|
||||
// slice.
|
||||
buf = make([]uint8, numEntries)
|
||||
for i := 0; i < numEntries; i++ {
|
||||
vv := v.Index(i)
|
||||
buf[i] = uint8(vv.Convert(uint8Type).Uint())
|
||||
}
|
||||
doHexDump = true
|
||||
}
|
||||
}
|
||||
|
||||
// Hexdump the entire slice as needed.
|
||||
if doHexDump {
|
||||
indent := strings.Repeat(d.cs.Indent, d.depth)
|
||||
str := indent + hex.Dump(buf)
|
||||
str = strings.Replace(str, "\n", "\n"+indent, -1)
|
||||
str = strings.TrimRight(str, d.cs.Indent)
|
||||
d.w.Write([]byte(str))
|
||||
return
|
||||
}
|
||||
|
||||
// Recursively call dump for each item.
|
||||
for i := 0; i < numEntries; i++ {
|
||||
d.dump(d.unpackValue(v.Index(i)))
|
||||
if i < (numEntries - 1) {
|
||||
d.w.Write(commaNewlineBytes)
|
||||
} else {
|
||||
d.w.Write(newlineBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dump is the main workhorse for dumping a value. It uses the passed reflect
|
||||
// value to figure out what kind of object we are dealing with and formats it
|
||||
// appropriately. It is a recursive function, however circular data structures
|
||||
// are detected and handled properly.
|
||||
func (d *dumpState) dump(v reflect.Value) {
|
||||
// Handle invalid reflect values immediately.
|
||||
kind := v.Kind()
|
||||
if kind == reflect.Invalid {
|
||||
d.w.Write(invalidAngleBytes)
|
||||
return
|
||||
}
|
||||
|
||||
// Handle pointers specially.
|
||||
if kind == reflect.Ptr {
|
||||
d.indent()
|
||||
d.dumpPtr(v)
|
||||
return
|
||||
}
|
||||
|
||||
// Print type information unless already handled elsewhere.
|
||||
if !d.ignoreNextType {
|
||||
d.indent()
|
||||
d.w.Write(openParenBytes)
|
||||
d.w.Write([]byte(v.Type().String()))
|
||||
d.w.Write(closeParenBytes)
|
||||
d.w.Write(spaceBytes)
|
||||
}
|
||||
d.ignoreNextType = false
|
||||
|
||||
// Display length and capacity if the built-in len and cap functions
|
||||
// work with the value's kind and the len/cap itself is non-zero.
|
||||
valueLen, valueCap := 0, 0
|
||||
switch v.Kind() {
|
||||
case reflect.Array, reflect.Slice, reflect.Chan:
|
||||
valueLen, valueCap = v.Len(), v.Cap()
|
||||
case reflect.Map, reflect.String:
|
||||
valueLen = v.Len()
|
||||
}
|
||||
if valueLen != 0 || !d.cs.DisableCapacities && valueCap != 0 {
|
||||
d.w.Write(openParenBytes)
|
||||
if valueLen != 0 {
|
||||
d.w.Write(lenEqualsBytes)
|
||||
printInt(d.w, int64(valueLen), 10)
|
||||
}
|
||||
if !d.cs.DisableCapacities && valueCap != 0 {
|
||||
if valueLen != 0 {
|
||||
d.w.Write(spaceBytes)
|
||||
}
|
||||
d.w.Write(capEqualsBytes)
|
||||
printInt(d.w, int64(valueCap), 10)
|
||||
}
|
||||
d.w.Write(closeParenBytes)
|
||||
d.w.Write(spaceBytes)
|
||||
}
|
||||
|
||||
// Call Stringer/error interfaces if they exist and the handle methods flag
|
||||
// is enabled
|
||||
if !d.cs.DisableMethods {
|
||||
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
|
||||
if handled := handleMethods(d.cs, d.w, v); handled {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch kind {
|
||||
case reflect.Invalid:
|
||||
// Do nothing. We should never get here since invalid has already
|
||||
// been handled above.
|
||||
|
||||
case reflect.Bool:
|
||||
printBool(d.w, v.Bool())
|
||||
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||
printInt(d.w, v.Int(), 10)
|
||||
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||
printUint(d.w, v.Uint(), 10)
|
||||
|
||||
case reflect.Float32:
|
||||
printFloat(d.w, v.Float(), 32)
|
||||
|
||||
case reflect.Float64:
|
||||
printFloat(d.w, v.Float(), 64)
|
||||
|
||||
case reflect.Complex64:
|
||||
printComplex(d.w, v.Complex(), 32)
|
||||
|
||||
case reflect.Complex128:
|
||||
printComplex(d.w, v.Complex(), 64)
|
||||
|
||||
case reflect.Slice:
|
||||
if v.IsNil() {
|
||||
d.w.Write(nilAngleBytes)
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
|
||||
case reflect.Array:
|
||||
d.w.Write(openBraceNewlineBytes)
|
||||
d.depth++
|
||||
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||
d.indent()
|
||||
d.w.Write(maxNewlineBytes)
|
||||
} else {
|
||||
d.dumpSlice(v)
|
||||
}
|
||||
d.depth--
|
||||
d.indent()
|
||||
d.w.Write(closeBraceBytes)
|
||||
|
||||
case reflect.String:
|
||||
d.w.Write([]byte(strconv.Quote(v.String())))
|
||||
|
||||
case reflect.Interface:
|
||||
// The only time we should get here is for nil interfaces due to
|
||||
// unpackValue calls.
|
||||
if v.IsNil() {
|
||||
d.w.Write(nilAngleBytes)
|
||||
}
|
||||
|
||||
case reflect.Ptr:
|
||||
// Do nothing. We should never get here since pointers have already
|
||||
// been handled above.
|
||||
|
||||
case reflect.Map:
|
||||
// nil maps should be indicated as different than empty maps
|
||||
if v.IsNil() {
|
||||
d.w.Write(nilAngleBytes)
|
||||
break
|
||||
}
|
||||
|
||||
d.w.Write(openBraceNewlineBytes)
|
||||
d.depth++
|
||||
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||
d.indent()
|
||||
d.w.Write(maxNewlineBytes)
|
||||
} else {
|
||||
numEntries := v.Len()
|
||||
keys := v.MapKeys()
|
||||
if d.cs.SortKeys {
|
||||
sortValues(keys, d.cs)
|
||||
}
|
||||
for i, key := range keys {
|
||||
d.dump(d.unpackValue(key))
|
||||
d.w.Write(colonSpaceBytes)
|
||||
d.ignoreNextIndent = true
|
||||
d.dump(d.unpackValue(v.MapIndex(key)))
|
||||
if i < (numEntries - 1) {
|
||||
d.w.Write(commaNewlineBytes)
|
||||
} else {
|
||||
d.w.Write(newlineBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
d.depth--
|
||||
d.indent()
|
||||
d.w.Write(closeBraceBytes)
|
||||
|
||||
case reflect.Struct:
|
||||
d.w.Write(openBraceNewlineBytes)
|
||||
d.depth++
|
||||
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||
d.indent()
|
||||
d.w.Write(maxNewlineBytes)
|
||||
} else {
|
||||
vt := v.Type()
|
||||
numFields := v.NumField()
|
||||
for i := 0; i < numFields; i++ {
|
||||
d.indent()
|
||||
vtf := vt.Field(i)
|
||||
d.w.Write([]byte(vtf.Name))
|
||||
d.w.Write(colonSpaceBytes)
|
||||
d.ignoreNextIndent = true
|
||||
d.dump(d.unpackValue(v.Field(i)))
|
||||
if i < (numFields - 1) {
|
||||
d.w.Write(commaNewlineBytes)
|
||||
} else {
|
||||
d.w.Write(newlineBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
d.depth--
|
||||
d.indent()
|
||||
d.w.Write(closeBraceBytes)
|
||||
|
||||
case reflect.Uintptr:
|
||||
printHexPtr(d.w, uintptr(v.Uint()))
|
||||
|
||||
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||
printHexPtr(d.w, v.Pointer())
|
||||
|
||||
// There were not any other types at the time this code was written, but
|
||||
// fall back to letting the default fmt package handle it in case any new
|
||||
// types are added.
|
||||
default:
|
||||
if v.CanInterface() {
|
||||
fmt.Fprintf(d.w, "%v", v.Interface())
|
||||
} else {
|
||||
fmt.Fprintf(d.w, "%v", v.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fdump is a helper function to consolidate the logic from the various public
|
||||
// methods which take varying writers and config states.
|
||||
func fdump(cs *ConfigState, w io.Writer, a ...interface{}) {
|
||||
for _, arg := range a {
|
||||
if arg == nil {
|
||||
w.Write(interfaceBytes)
|
||||
w.Write(spaceBytes)
|
||||
w.Write(nilAngleBytes)
|
||||
w.Write(newlineBytes)
|
||||
continue
|
||||
}
|
||||
|
||||
d := dumpState{w: w, cs: cs}
|
||||
d.pointers = make(map[uintptr]int)
|
||||
d.dump(reflect.ValueOf(arg))
|
||||
d.w.Write(newlineBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
||||
// exactly the same as Dump.
|
||||
func Fdump(w io.Writer, a ...interface{}) {
|
||||
fdump(&Config, w, a...)
|
||||
}
|
||||
|
||||
// Sdump returns a string with the passed arguments formatted exactly the same
|
||||
// as Dump.
|
||||
func Sdump(a ...interface{}) string {
|
||||
var buf bytes.Buffer
|
||||
fdump(&Config, &buf, a...)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
/*
|
||||
Dump displays the passed parameters to standard out with newlines, customizable
|
||||
indentation, and additional debug information such as complete types and all
|
||||
pointer addresses used to indirect to the final value. It provides the
|
||||
following features over the built-in printing facilities provided by the fmt
|
||||
package:
|
||||
|
||||
* Pointers are dereferenced and followed
|
||||
* Circular data structures are detected and handled properly
|
||||
* Custom Stringer/error interfaces are optionally invoked, including
|
||||
on unexported types
|
||||
* Custom types which only implement the Stringer/error interfaces via
|
||||
a pointer receiver are optionally invoked when passing non-pointer
|
||||
variables
|
||||
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||
includes offsets, byte values in hex, and ASCII output
|
||||
|
||||
The configuration options are controlled by an exported package global,
|
||||
spew.Config. See ConfigState for options documentation.
|
||||
|
||||
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
||||
get the formatted result as a string.
|
||||
*/
|
||||
func Dump(a ...interface{}) {
|
||||
fdump(&Config, os.Stdout, a...)
|
||||
}
|
||||
419
vendor/github.com/davecgh/go-spew/spew/format.go
generated
vendored
419
vendor/github.com/davecgh/go-spew/spew/format.go
generated
vendored
|
|
@ -1,419 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// supportedFlags is a list of all the character flags supported by fmt package.
|
||||
const supportedFlags = "0-+# "
|
||||
|
||||
// formatState implements the fmt.Formatter interface and contains information
|
||||
// about the state of a formatting operation. The NewFormatter function can
|
||||
// be used to get a new Formatter which can be used directly as arguments
|
||||
// in standard fmt package printing calls.
|
||||
type formatState struct {
|
||||
value interface{}
|
||||
fs fmt.State
|
||||
depth int
|
||||
pointers map[uintptr]int
|
||||
ignoreNextType bool
|
||||
cs *ConfigState
|
||||
}
|
||||
|
||||
// buildDefaultFormat recreates the original format string without precision
|
||||
// and width information to pass in to fmt.Sprintf in the case of an
|
||||
// unrecognized type. Unless new types are added to the language, this
|
||||
// function won't ever be called.
|
||||
func (f *formatState) buildDefaultFormat() (format string) {
|
||||
buf := bytes.NewBuffer(percentBytes)
|
||||
|
||||
for _, flag := range supportedFlags {
|
||||
if f.fs.Flag(int(flag)) {
|
||||
buf.WriteRune(flag)
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteRune('v')
|
||||
|
||||
format = buf.String()
|
||||
return format
|
||||
}
|
||||
|
||||
// constructOrigFormat recreates the original format string including precision
|
||||
// and width information to pass along to the standard fmt package. This allows
|
||||
// automatic deferral of all format strings this package doesn't support.
|
||||
func (f *formatState) constructOrigFormat(verb rune) (format string) {
|
||||
buf := bytes.NewBuffer(percentBytes)
|
||||
|
||||
for _, flag := range supportedFlags {
|
||||
if f.fs.Flag(int(flag)) {
|
||||
buf.WriteRune(flag)
|
||||
}
|
||||
}
|
||||
|
||||
if width, ok := f.fs.Width(); ok {
|
||||
buf.WriteString(strconv.Itoa(width))
|
||||
}
|
||||
|
||||
if precision, ok := f.fs.Precision(); ok {
|
||||
buf.Write(precisionBytes)
|
||||
buf.WriteString(strconv.Itoa(precision))
|
||||
}
|
||||
|
||||
buf.WriteRune(verb)
|
||||
|
||||
format = buf.String()
|
||||
return format
|
||||
}
|
||||
|
||||
// unpackValue returns values inside of non-nil interfaces when possible and
|
||||
// ensures that types for values which have been unpacked from an interface
|
||||
// are displayed when the show types flag is also set.
|
||||
// This is useful for data types like structs, arrays, slices, and maps which
|
||||
// can contain varying types packed inside an interface.
|
||||
func (f *formatState) unpackValue(v reflect.Value) reflect.Value {
|
||||
if v.Kind() == reflect.Interface {
|
||||
f.ignoreNextType = false
|
||||
if !v.IsNil() {
|
||||
v = v.Elem()
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// formatPtr handles formatting of pointers by indirecting them as necessary.
|
||||
func (f *formatState) formatPtr(v reflect.Value) {
|
||||
// Display nil if top level pointer is nil.
|
||||
showTypes := f.fs.Flag('#')
|
||||
if v.IsNil() && (!showTypes || f.ignoreNextType) {
|
||||
f.fs.Write(nilAngleBytes)
|
||||
return
|
||||
}
|
||||
|
||||
// Remove pointers at or below the current depth from map used to detect
|
||||
// circular refs.
|
||||
for k, depth := range f.pointers {
|
||||
if depth >= f.depth {
|
||||
delete(f.pointers, k)
|
||||
}
|
||||
}
|
||||
|
||||
// Keep list of all dereferenced pointers to possibly show later.
|
||||
pointerChain := make([]uintptr, 0)
|
||||
|
||||
// Figure out how many levels of indirection there are by derferencing
|
||||
// pointers and unpacking interfaces down the chain while detecting circular
|
||||
// references.
|
||||
nilFound := false
|
||||
cycleFound := false
|
||||
indirects := 0
|
||||
ve := v
|
||||
for ve.Kind() == reflect.Ptr {
|
||||
if ve.IsNil() {
|
||||
nilFound = true
|
||||
break
|
||||
}
|
||||
indirects++
|
||||
addr := ve.Pointer()
|
||||
pointerChain = append(pointerChain, addr)
|
||||
if pd, ok := f.pointers[addr]; ok && pd < f.depth {
|
||||
cycleFound = true
|
||||
indirects--
|
||||
break
|
||||
}
|
||||
f.pointers[addr] = f.depth
|
||||
|
||||
ve = ve.Elem()
|
||||
if ve.Kind() == reflect.Interface {
|
||||
if ve.IsNil() {
|
||||
nilFound = true
|
||||
break
|
||||
}
|
||||
ve = ve.Elem()
|
||||
}
|
||||
}
|
||||
|
||||
// Display type or indirection level depending on flags.
|
||||
if showTypes && !f.ignoreNextType {
|
||||
f.fs.Write(openParenBytes)
|
||||
f.fs.Write(bytes.Repeat(asteriskBytes, indirects))
|
||||
f.fs.Write([]byte(ve.Type().String()))
|
||||
f.fs.Write(closeParenBytes)
|
||||
} else {
|
||||
if nilFound || cycleFound {
|
||||
indirects += strings.Count(ve.Type().String(), "*")
|
||||
}
|
||||
f.fs.Write(openAngleBytes)
|
||||
f.fs.Write([]byte(strings.Repeat("*", indirects)))
|
||||
f.fs.Write(closeAngleBytes)
|
||||
}
|
||||
|
||||
// Display pointer information depending on flags.
|
||||
if f.fs.Flag('+') && (len(pointerChain) > 0) {
|
||||
f.fs.Write(openParenBytes)
|
||||
for i, addr := range pointerChain {
|
||||
if i > 0 {
|
||||
f.fs.Write(pointerChainBytes)
|
||||
}
|
||||
printHexPtr(f.fs, addr)
|
||||
}
|
||||
f.fs.Write(closeParenBytes)
|
||||
}
|
||||
|
||||
// Display dereferenced value.
|
||||
switch {
|
||||
case nilFound == true:
|
||||
f.fs.Write(nilAngleBytes)
|
||||
|
||||
case cycleFound == true:
|
||||
f.fs.Write(circularShortBytes)
|
||||
|
||||
default:
|
||||
f.ignoreNextType = true
|
||||
f.format(ve)
|
||||
}
|
||||
}
|
||||
|
||||
// format is the main workhorse for providing the Formatter interface. It
|
||||
// uses the passed reflect value to figure out what kind of object we are
|
||||
// dealing with and formats it appropriately. It is a recursive function,
|
||||
// however circular data structures are detected and handled properly.
|
||||
func (f *formatState) format(v reflect.Value) {
|
||||
// Handle invalid reflect values immediately.
|
||||
kind := v.Kind()
|
||||
if kind == reflect.Invalid {
|
||||
f.fs.Write(invalidAngleBytes)
|
||||
return
|
||||
}
|
||||
|
||||
// Handle pointers specially.
|
||||
if kind == reflect.Ptr {
|
||||
f.formatPtr(v)
|
||||
return
|
||||
}
|
||||
|
||||
// Print type information unless already handled elsewhere.
|
||||
if !f.ignoreNextType && f.fs.Flag('#') {
|
||||
f.fs.Write(openParenBytes)
|
||||
f.fs.Write([]byte(v.Type().String()))
|
||||
f.fs.Write(closeParenBytes)
|
||||
}
|
||||
f.ignoreNextType = false
|
||||
|
||||
// Call Stringer/error interfaces if they exist and the handle methods
|
||||
// flag is enabled.
|
||||
if !f.cs.DisableMethods {
|
||||
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
|
||||
if handled := handleMethods(f.cs, f.fs, v); handled {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch kind {
|
||||
case reflect.Invalid:
|
||||
// Do nothing. We should never get here since invalid has already
|
||||
// been handled above.
|
||||
|
||||
case reflect.Bool:
|
||||
printBool(f.fs, v.Bool())
|
||||
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||
printInt(f.fs, v.Int(), 10)
|
||||
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||
printUint(f.fs, v.Uint(), 10)
|
||||
|
||||
case reflect.Float32:
|
||||
printFloat(f.fs, v.Float(), 32)
|
||||
|
||||
case reflect.Float64:
|
||||
printFloat(f.fs, v.Float(), 64)
|
||||
|
||||
case reflect.Complex64:
|
||||
printComplex(f.fs, v.Complex(), 32)
|
||||
|
||||
case reflect.Complex128:
|
||||
printComplex(f.fs, v.Complex(), 64)
|
||||
|
||||
case reflect.Slice:
|
||||
if v.IsNil() {
|
||||
f.fs.Write(nilAngleBytes)
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
|
||||
case reflect.Array:
|
||||
f.fs.Write(openBracketBytes)
|
||||
f.depth++
|
||||
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||
f.fs.Write(maxShortBytes)
|
||||
} else {
|
||||
numEntries := v.Len()
|
||||
for i := 0; i < numEntries; i++ {
|
||||
if i > 0 {
|
||||
f.fs.Write(spaceBytes)
|
||||
}
|
||||
f.ignoreNextType = true
|
||||
f.format(f.unpackValue(v.Index(i)))
|
||||
}
|
||||
}
|
||||
f.depth--
|
||||
f.fs.Write(closeBracketBytes)
|
||||
|
||||
case reflect.String:
|
||||
f.fs.Write([]byte(v.String()))
|
||||
|
||||
case reflect.Interface:
|
||||
// The only time we should get here is for nil interfaces due to
|
||||
// unpackValue calls.
|
||||
if v.IsNil() {
|
||||
f.fs.Write(nilAngleBytes)
|
||||
}
|
||||
|
||||
case reflect.Ptr:
|
||||
// Do nothing. We should never get here since pointers have already
|
||||
// been handled above.
|
||||
|
||||
case reflect.Map:
|
||||
// nil maps should be indicated as different than empty maps
|
||||
if v.IsNil() {
|
||||
f.fs.Write(nilAngleBytes)
|
||||
break
|
||||
}
|
||||
|
||||
f.fs.Write(openMapBytes)
|
||||
f.depth++
|
||||
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||
f.fs.Write(maxShortBytes)
|
||||
} else {
|
||||
keys := v.MapKeys()
|
||||
if f.cs.SortKeys {
|
||||
sortValues(keys, f.cs)
|
||||
}
|
||||
for i, key := range keys {
|
||||
if i > 0 {
|
||||
f.fs.Write(spaceBytes)
|
||||
}
|
||||
f.ignoreNextType = true
|
||||
f.format(f.unpackValue(key))
|
||||
f.fs.Write(colonBytes)
|
||||
f.ignoreNextType = true
|
||||
f.format(f.unpackValue(v.MapIndex(key)))
|
||||
}
|
||||
}
|
||||
f.depth--
|
||||
f.fs.Write(closeMapBytes)
|
||||
|
||||
case reflect.Struct:
|
||||
numFields := v.NumField()
|
||||
f.fs.Write(openBraceBytes)
|
||||
f.depth++
|
||||
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||
f.fs.Write(maxShortBytes)
|
||||
} else {
|
||||
vt := v.Type()
|
||||
for i := 0; i < numFields; i++ {
|
||||
if i > 0 {
|
||||
f.fs.Write(spaceBytes)
|
||||
}
|
||||
vtf := vt.Field(i)
|
||||
if f.fs.Flag('+') || f.fs.Flag('#') {
|
||||
f.fs.Write([]byte(vtf.Name))
|
||||
f.fs.Write(colonBytes)
|
||||
}
|
||||
f.format(f.unpackValue(v.Field(i)))
|
||||
}
|
||||
}
|
||||
f.depth--
|
||||
f.fs.Write(closeBraceBytes)
|
||||
|
||||
case reflect.Uintptr:
|
||||
printHexPtr(f.fs, uintptr(v.Uint()))
|
||||
|
||||
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||
printHexPtr(f.fs, v.Pointer())
|
||||
|
||||
// There were not any other types at the time this code was written, but
|
||||
// fall back to letting the default fmt package handle it if any get added.
|
||||
default:
|
||||
format := f.buildDefaultFormat()
|
||||
if v.CanInterface() {
|
||||
fmt.Fprintf(f.fs, format, v.Interface())
|
||||
} else {
|
||||
fmt.Fprintf(f.fs, format, v.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Format satisfies the fmt.Formatter interface. See NewFormatter for usage
|
||||
// details.
|
||||
func (f *formatState) Format(fs fmt.State, verb rune) {
|
||||
f.fs = fs
|
||||
|
||||
// Use standard formatting for verbs that are not v.
|
||||
if verb != 'v' {
|
||||
format := f.constructOrigFormat(verb)
|
||||
fmt.Fprintf(fs, format, f.value)
|
||||
return
|
||||
}
|
||||
|
||||
if f.value == nil {
|
||||
if fs.Flag('#') {
|
||||
fs.Write(interfaceBytes)
|
||||
}
|
||||
fs.Write(nilAngleBytes)
|
||||
return
|
||||
}
|
||||
|
||||
f.format(reflect.ValueOf(f.value))
|
||||
}
|
||||
|
||||
// newFormatter is a helper function to consolidate the logic from the various
|
||||
// public methods which take varying config states.
|
||||
func newFormatter(cs *ConfigState, v interface{}) fmt.Formatter {
|
||||
fs := &formatState{value: v, cs: cs}
|
||||
fs.pointers = make(map[uintptr]int)
|
||||
return fs
|
||||
}
|
||||
|
||||
/*
|
||||
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
|
||||
interface. As a result, it integrates cleanly with standard fmt package
|
||||
printing functions. The formatter is useful for inline printing of smaller data
|
||||
types similar to the standard %v format specifier.
|
||||
|
||||
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
|
||||
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||
the width and precision arguments (however they will still work on the format
|
||||
specifiers not handled by the custom formatter).
|
||||
|
||||
Typically this function shouldn't be called directly. It is much easier to make
|
||||
use of the custom formatter by calling one of the convenience functions such as
|
||||
Printf, Println, or Fprintf.
|
||||
*/
|
||||
func NewFormatter(v interface{}) fmt.Formatter {
|
||||
return newFormatter(&Config, v)
|
||||
}
|
||||
148
vendor/github.com/davecgh/go-spew/spew/spew.go
generated
vendored
148
vendor/github.com/davecgh/go-spew/spew/spew.go
generated
vendored
|
|
@ -1,148 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the formatted string as a value that satisfies error. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Errorf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Errorf(format string, a ...interface{}) (err error) {
|
||||
return fmt.Errorf(format, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprint(w, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprint(w, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprintf(w, format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintf(w, format, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
|
||||
// passed with a default Formatter interface returned by NewFormatter. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprintln(w, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintln(w, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Print is a wrapper for fmt.Print that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Print(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Print(a ...interface{}) (n int, err error) {
|
||||
return fmt.Print(convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Printf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Printf(format string, a ...interface{}) (n int, err error) {
|
||||
return fmt.Printf(format, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Println is a wrapper for fmt.Println that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Println(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Println(a ...interface{}) (n int, err error) {
|
||||
return fmt.Println(convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprint(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Sprint(a ...interface{}) string {
|
||||
return fmt.Sprint(convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprintf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Sprintf(format string, a ...interface{}) string {
|
||||
return fmt.Sprintf(format, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
|
||||
// were passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprintln(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Sprintln(a ...interface{}) string {
|
||||
return fmt.Sprintln(convertArgs(a)...)
|
||||
}
|
||||
|
||||
// convertArgs accepts a slice of arguments and returns a slice of the same
|
||||
// length with each argument converted to a default spew Formatter interface.
|
||||
func convertArgs(args []interface{}) (formatters []interface{}) {
|
||||
formatters = make([]interface{}, len(args))
|
||||
for index, arg := range args {
|
||||
formatters[index] = NewFormatter(arg)
|
||||
}
|
||||
return formatters
|
||||
}
|
||||
20
vendor/github.com/fatih/color/LICENSE.md
generated
vendored
20
vendor/github.com/fatih/color/LICENSE.md
generated
vendored
|
|
@ -1,20 +0,0 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Fatih Arslan
|
||||
|
||||
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.
|
||||
600
vendor/github.com/fatih/color/color.go
generated
vendored
600
vendor/github.com/fatih/color/color.go
generated
vendored
|
|
@ -1,600 +0,0 @@
|
|||
package color
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/mattn/go-colorable"
|
||||
"github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
var (
|
||||
// NoColor defines if the output is colorized or not. It's dynamically set to
|
||||
// false or true based on the stdout's file descriptor referring to a terminal
|
||||
// or not. This is a global option and affects all colors. For more control
|
||||
// over each color block use the methods DisableColor() individually.
|
||||
NoColor = os.Getenv("TERM") == "dumb" ||
|
||||
(!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd()))
|
||||
|
||||
// Output defines the standard output of the print functions. By default
|
||||
// os.Stdout is used.
|
||||
Output = colorable.NewColorableStdout()
|
||||
|
||||
// colorsCache is used to reduce the count of created Color objects and
|
||||
// allows to reuse already created objects with required Attribute.
|
||||
colorsCache = make(map[Attribute]*Color)
|
||||
colorsCacheMu sync.Mutex // protects colorsCache
|
||||
)
|
||||
|
||||
// Color defines a custom color object which is defined by SGR parameters.
|
||||
type Color struct {
|
||||
params []Attribute
|
||||
noColor *bool
|
||||
}
|
||||
|
||||
// Attribute defines a single SGR Code
|
||||
type Attribute int
|
||||
|
||||
const escape = "\x1b"
|
||||
|
||||
// Base attributes
|
||||
const (
|
||||
Reset Attribute = iota
|
||||
Bold
|
||||
Faint
|
||||
Italic
|
||||
Underline
|
||||
BlinkSlow
|
||||
BlinkRapid
|
||||
ReverseVideo
|
||||
Concealed
|
||||
CrossedOut
|
||||
)
|
||||
|
||||
// Foreground text colors
|
||||
const (
|
||||
FgBlack Attribute = iota + 30
|
||||
FgRed
|
||||
FgGreen
|
||||
FgYellow
|
||||
FgBlue
|
||||
FgMagenta
|
||||
FgCyan
|
||||
FgWhite
|
||||
)
|
||||
|
||||
// Foreground Hi-Intensity text colors
|
||||
const (
|
||||
FgHiBlack Attribute = iota + 90
|
||||
FgHiRed
|
||||
FgHiGreen
|
||||
FgHiYellow
|
||||
FgHiBlue
|
||||
FgHiMagenta
|
||||
FgHiCyan
|
||||
FgHiWhite
|
||||
)
|
||||
|
||||
// Background text colors
|
||||
const (
|
||||
BgBlack Attribute = iota + 40
|
||||
BgRed
|
||||
BgGreen
|
||||
BgYellow
|
||||
BgBlue
|
||||
BgMagenta
|
||||
BgCyan
|
||||
BgWhite
|
||||
)
|
||||
|
||||
// Background Hi-Intensity text colors
|
||||
const (
|
||||
BgHiBlack Attribute = iota + 100
|
||||
BgHiRed
|
||||
BgHiGreen
|
||||
BgHiYellow
|
||||
BgHiBlue
|
||||
BgHiMagenta
|
||||
BgHiCyan
|
||||
BgHiWhite
|
||||
)
|
||||
|
||||
// New returns a newly created color object.
|
||||
func New(value ...Attribute) *Color {
|
||||
c := &Color{params: make([]Attribute, 0)}
|
||||
c.Add(value...)
|
||||
return c
|
||||
}
|
||||
|
||||
// Set sets the given parameters immediately. It will change the color of
|
||||
// output with the given SGR parameters until color.Unset() is called.
|
||||
func Set(p ...Attribute) *Color {
|
||||
c := New(p...)
|
||||
c.Set()
|
||||
return c
|
||||
}
|
||||
|
||||
// Unset resets all escape attributes and clears the output. Usually should
|
||||
// be called after Set().
|
||||
func Unset() {
|
||||
if NoColor {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(Output, "%s[%dm", escape, Reset)
|
||||
}
|
||||
|
||||
// Set sets the SGR sequence.
|
||||
func (c *Color) Set() *Color {
|
||||
if c.isNoColorSet() {
|
||||
return c
|
||||
}
|
||||
|
||||
fmt.Fprintf(Output, c.format())
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Color) unset() {
|
||||
if c.isNoColorSet() {
|
||||
return
|
||||
}
|
||||
|
||||
Unset()
|
||||
}
|
||||
|
||||
func (c *Color) setWriter(w io.Writer) *Color {
|
||||
if c.isNoColorSet() {
|
||||
return c
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, c.format())
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Color) unsetWriter(w io.Writer) {
|
||||
if c.isNoColorSet() {
|
||||
return
|
||||
}
|
||||
|
||||
if NoColor {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "%s[%dm", escape, Reset)
|
||||
}
|
||||
|
||||
// Add is used to chain SGR parameters. Use as many as parameters to combine
|
||||
// and create custom color objects. Example: Add(color.FgRed, color.Underline).
|
||||
func (c *Color) Add(value ...Attribute) *Color {
|
||||
c.params = append(c.params, value...)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Color) prepend(value Attribute) {
|
||||
c.params = append(c.params, 0)
|
||||
copy(c.params[1:], c.params[0:])
|
||||
c.params[0] = value
|
||||
}
|
||||
|
||||
// Fprint formats using the default formats for its operands and writes to w.
|
||||
// Spaces are added between operands when neither is a string.
|
||||
// It returns the number of bytes written and any write error encountered.
|
||||
// On Windows, users should wrap w with colorable.NewColorable() if w is of
|
||||
// type *os.File.
|
||||
func (c *Color) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
c.setWriter(w)
|
||||
defer c.unsetWriter(w)
|
||||
|
||||
return fmt.Fprint(w, a...)
|
||||
}
|
||||
|
||||
// Print formats using the default formats for its operands and writes to
|
||||
// standard output. Spaces are added between operands when neither is a
|
||||
// string. It returns the number of bytes written and any write error
|
||||
// encountered. This is the standard fmt.Print() method wrapped with the given
|
||||
// color.
|
||||
func (c *Color) Print(a ...interface{}) (n int, err error) {
|
||||
c.Set()
|
||||
defer c.unset()
|
||||
|
||||
return fmt.Fprint(Output, a...)
|
||||
}
|
||||
|
||||
// Fprintf formats according to a format specifier and writes to w.
|
||||
// It returns the number of bytes written and any write error encountered.
|
||||
// On Windows, users should wrap w with colorable.NewColorable() if w is of
|
||||
// type *os.File.
|
||||
func (c *Color) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
||||
c.setWriter(w)
|
||||
defer c.unsetWriter(w)
|
||||
|
||||
return fmt.Fprintf(w, format, a...)
|
||||
}
|
||||
|
||||
// Printf formats according to a format specifier and writes to standard output.
|
||||
// It returns the number of bytes written and any write error encountered.
|
||||
// This is the standard fmt.Printf() method wrapped with the given color.
|
||||
func (c *Color) Printf(format string, a ...interface{}) (n int, err error) {
|
||||
c.Set()
|
||||
defer c.unset()
|
||||
|
||||
return fmt.Fprintf(Output, format, a...)
|
||||
}
|
||||
|
||||
// Fprintln formats using the default formats for its operands and writes to w.
|
||||
// Spaces are always added between operands and a newline is appended.
|
||||
// On Windows, users should wrap w with colorable.NewColorable() if w is of
|
||||
// type *os.File.
|
||||
func (c *Color) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
c.setWriter(w)
|
||||
defer c.unsetWriter(w)
|
||||
|
||||
return fmt.Fprintln(w, a...)
|
||||
}
|
||||
|
||||
// Println formats using the default formats for its operands and writes to
|
||||
// standard output. Spaces are always added between operands and a newline is
|
||||
// appended. It returns the number of bytes written and any write error
|
||||
// encountered. This is the standard fmt.Print() method wrapped with the given
|
||||
// color.
|
||||
func (c *Color) Println(a ...interface{}) (n int, err error) {
|
||||
c.Set()
|
||||
defer c.unset()
|
||||
|
||||
return fmt.Fprintln(Output, a...)
|
||||
}
|
||||
|
||||
// Sprint is just like Print, but returns a string instead of printing it.
|
||||
func (c *Color) Sprint(a ...interface{}) string {
|
||||
return c.wrap(fmt.Sprint(a...))
|
||||
}
|
||||
|
||||
// Sprintln is just like Println, but returns a string instead of printing it.
|
||||
func (c *Color) Sprintln(a ...interface{}) string {
|
||||
return c.wrap(fmt.Sprintln(a...))
|
||||
}
|
||||
|
||||
// Sprintf is just like Printf, but returns a string instead of printing it.
|
||||
func (c *Color) Sprintf(format string, a ...interface{}) string {
|
||||
return c.wrap(fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// FprintFunc returns a new function that prints the passed arguments as
|
||||
// colorized with color.Fprint().
|
||||
func (c *Color) FprintFunc() func(w io.Writer, a ...interface{}) {
|
||||
return func(w io.Writer, a ...interface{}) {
|
||||
c.Fprint(w, a...)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintFunc returns a new function that prints the passed arguments as
|
||||
// colorized with color.Print().
|
||||
func (c *Color) PrintFunc() func(a ...interface{}) {
|
||||
return func(a ...interface{}) {
|
||||
c.Print(a...)
|
||||
}
|
||||
}
|
||||
|
||||
// FprintfFunc returns a new function that prints the passed arguments as
|
||||
// colorized with color.Fprintf().
|
||||
func (c *Color) FprintfFunc() func(w io.Writer, format string, a ...interface{}) {
|
||||
return func(w io.Writer, format string, a ...interface{}) {
|
||||
c.Fprintf(w, format, a...)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintfFunc returns a new function that prints the passed arguments as
|
||||
// colorized with color.Printf().
|
||||
func (c *Color) PrintfFunc() func(format string, a ...interface{}) {
|
||||
return func(format string, a ...interface{}) {
|
||||
c.Printf(format, a...)
|
||||
}
|
||||
}
|
||||
|
||||
// FprintlnFunc returns a new function that prints the passed arguments as
|
||||
// colorized with color.Fprintln().
|
||||
func (c *Color) FprintlnFunc() func(w io.Writer, a ...interface{}) {
|
||||
return func(w io.Writer, a ...interface{}) {
|
||||
c.Fprintln(w, a...)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintlnFunc returns a new function that prints the passed arguments as
|
||||
// colorized with color.Println().
|
||||
func (c *Color) PrintlnFunc() func(a ...interface{}) {
|
||||
return func(a ...interface{}) {
|
||||
c.Println(a...)
|
||||
}
|
||||
}
|
||||
|
||||
// SprintFunc returns a new function that returns colorized strings for the
|
||||
// given arguments with fmt.Sprint(). Useful to put into or mix into other
|
||||
// string. Windows users should use this in conjunction with color.Output, example:
|
||||
//
|
||||
// put := New(FgYellow).SprintFunc()
|
||||
// fmt.Fprintf(color.Output, "This is a %s", put("warning"))
|
||||
func (c *Color) SprintFunc() func(a ...interface{}) string {
|
||||
return func(a ...interface{}) string {
|
||||
return c.wrap(fmt.Sprint(a...))
|
||||
}
|
||||
}
|
||||
|
||||
// SprintfFunc returns a new function that returns colorized strings for the
|
||||
// given arguments with fmt.Sprintf(). Useful to put into or mix into other
|
||||
// string. Windows users should use this in conjunction with color.Output.
|
||||
func (c *Color) SprintfFunc() func(format string, a ...interface{}) string {
|
||||
return func(format string, a ...interface{}) string {
|
||||
return c.wrap(fmt.Sprintf(format, a...))
|
||||
}
|
||||
}
|
||||
|
||||
// SprintlnFunc returns a new function that returns colorized strings for the
|
||||
// given arguments with fmt.Sprintln(). Useful to put into or mix into other
|
||||
// string. Windows users should use this in conjunction with color.Output.
|
||||
func (c *Color) SprintlnFunc() func(a ...interface{}) string {
|
||||
return func(a ...interface{}) string {
|
||||
return c.wrap(fmt.Sprintln(a...))
|
||||
}
|
||||
}
|
||||
|
||||
// sequence returns a formatted SGR sequence to be plugged into a "\x1b[...m"
|
||||
// an example output might be: "1;36" -> bold cyan
|
||||
func (c *Color) sequence() string {
|
||||
format := make([]string, len(c.params))
|
||||
for i, v := range c.params {
|
||||
format[i] = strconv.Itoa(int(v))
|
||||
}
|
||||
|
||||
return strings.Join(format, ";")
|
||||
}
|
||||
|
||||
// wrap wraps the s string with the colors attributes. The string is ready to
|
||||
// be printed.
|
||||
func (c *Color) wrap(s string) string {
|
||||
if c.isNoColorSet() {
|
||||
return s
|
||||
}
|
||||
|
||||
return c.format() + s + c.unformat()
|
||||
}
|
||||
|
||||
func (c *Color) format() string {
|
||||
return fmt.Sprintf("%s[%sm", escape, c.sequence())
|
||||
}
|
||||
|
||||
func (c *Color) unformat() string {
|
||||
return fmt.Sprintf("%s[%dm", escape, Reset)
|
||||
}
|
||||
|
||||
// DisableColor disables the color output. Useful to not change any existing
|
||||
// code and still being able to output. Can be used for flags like
|
||||
// "--no-color". To enable back use EnableColor() method.
|
||||
func (c *Color) DisableColor() {
|
||||
c.noColor = boolPtr(true)
|
||||
}
|
||||
|
||||
// EnableColor enables the color output. Use it in conjunction with
|
||||
// DisableColor(). Otherwise this method has no side effects.
|
||||
func (c *Color) EnableColor() {
|
||||
c.noColor = boolPtr(false)
|
||||
}
|
||||
|
||||
func (c *Color) isNoColorSet() bool {
|
||||
// check first if we have user setted action
|
||||
if c.noColor != nil {
|
||||
return *c.noColor
|
||||
}
|
||||
|
||||
// if not return the global option, which is disabled by default
|
||||
return NoColor
|
||||
}
|
||||
|
||||
// Equals returns a boolean value indicating whether two colors are equal.
|
||||
func (c *Color) Equals(c2 *Color) bool {
|
||||
if len(c.params) != len(c2.params) {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, attr := range c.params {
|
||||
if !c2.attrExists(attr) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *Color) attrExists(a Attribute) bool {
|
||||
for _, attr := range c.params {
|
||||
if attr == a {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func boolPtr(v bool) *bool {
|
||||
return &v
|
||||
}
|
||||
|
||||
func getCachedColor(p Attribute) *Color {
|
||||
colorsCacheMu.Lock()
|
||||
defer colorsCacheMu.Unlock()
|
||||
|
||||
c, ok := colorsCache[p]
|
||||
if !ok {
|
||||
c = New(p)
|
||||
colorsCache[p] = c
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func colorPrint(format string, p Attribute, a ...interface{}) {
|
||||
c := getCachedColor(p)
|
||||
|
||||
if !strings.HasSuffix(format, "\n") {
|
||||
format += "\n"
|
||||
}
|
||||
|
||||
if len(a) == 0 {
|
||||
c.Print(format)
|
||||
} else {
|
||||
c.Printf(format, a...)
|
||||
}
|
||||
}
|
||||
|
||||
func colorString(format string, p Attribute, a ...interface{}) string {
|
||||
c := getCachedColor(p)
|
||||
|
||||
if len(a) == 0 {
|
||||
return c.SprintFunc()(format)
|
||||
}
|
||||
|
||||
return c.SprintfFunc()(format, a...)
|
||||
}
|
||||
|
||||
// Black is a convenient helper function to print with black foreground. A
|
||||
// newline is appended to format by default.
|
||||
func Black(format string, a ...interface{}) { colorPrint(format, FgBlack, a...) }
|
||||
|
||||
// Red is a convenient helper function to print with red foreground. A
|
||||
// newline is appended to format by default.
|
||||
func Red(format string, a ...interface{}) { colorPrint(format, FgRed, a...) }
|
||||
|
||||
// Green is a convenient helper function to print with green foreground. A
|
||||
// newline is appended to format by default.
|
||||
func Green(format string, a ...interface{}) { colorPrint(format, FgGreen, a...) }
|
||||
|
||||
// Yellow is a convenient helper function to print with yellow foreground.
|
||||
// A newline is appended to format by default.
|
||||
func Yellow(format string, a ...interface{}) { colorPrint(format, FgYellow, a...) }
|
||||
|
||||
// Blue is a convenient helper function to print with blue foreground. A
|
||||
// newline is appended to format by default.
|
||||
func Blue(format string, a ...interface{}) { colorPrint(format, FgBlue, a...) }
|
||||
|
||||
// Magenta is a convenient helper function to print with magenta foreground.
|
||||
// A newline is appended to format by default.
|
||||
func Magenta(format string, a ...interface{}) { colorPrint(format, FgMagenta, a...) }
|
||||
|
||||
// Cyan is a convenient helper function to print with cyan foreground. A
|
||||
// newline is appended to format by default.
|
||||
func Cyan(format string, a ...interface{}) { colorPrint(format, FgCyan, a...) }
|
||||
|
||||
// White is a convenient helper function to print with white foreground. A
|
||||
// newline is appended to format by default.
|
||||
func White(format string, a ...interface{}) { colorPrint(format, FgWhite, a...) }
|
||||
|
||||
// BlackString is a convenient helper function to return a string with black
|
||||
// foreground.
|
||||
func BlackString(format string, a ...interface{}) string { return colorString(format, FgBlack, a...) }
|
||||
|
||||
// RedString is a convenient helper function to return a string with red
|
||||
// foreground.
|
||||
func RedString(format string, a ...interface{}) string { return colorString(format, FgRed, a...) }
|
||||
|
||||
// GreenString is a convenient helper function to return a string with green
|
||||
// foreground.
|
||||
func GreenString(format string, a ...interface{}) string { return colorString(format, FgGreen, a...) }
|
||||
|
||||
// YellowString is a convenient helper function to return a string with yellow
|
||||
// foreground.
|
||||
func YellowString(format string, a ...interface{}) string { return colorString(format, FgYellow, a...) }
|
||||
|
||||
// BlueString is a convenient helper function to return a string with blue
|
||||
// foreground.
|
||||
func BlueString(format string, a ...interface{}) string { return colorString(format, FgBlue, a...) }
|
||||
|
||||
// MagentaString is a convenient helper function to return a string with magenta
|
||||
// foreground.
|
||||
func MagentaString(format string, a ...interface{}) string {
|
||||
return colorString(format, FgMagenta, a...)
|
||||
}
|
||||
|
||||
// CyanString is a convenient helper function to return a string with cyan
|
||||
// foreground.
|
||||
func CyanString(format string, a ...interface{}) string { return colorString(format, FgCyan, a...) }
|
||||
|
||||
// WhiteString is a convenient helper function to return a string with white
|
||||
// foreground.
|
||||
func WhiteString(format string, a ...interface{}) string { return colorString(format, FgWhite, a...) }
|
||||
|
||||
// HiBlack is a convenient helper function to print with hi-intensity black foreground. A
|
||||
// newline is appended to format by default.
|
||||
func HiBlack(format string, a ...interface{}) { colorPrint(format, FgHiBlack, a...) }
|
||||
|
||||
// HiRed is a convenient helper function to print with hi-intensity red foreground. A
|
||||
// newline is appended to format by default.
|
||||
func HiRed(format string, a ...interface{}) { colorPrint(format, FgHiRed, a...) }
|
||||
|
||||
// HiGreen is a convenient helper function to print with hi-intensity green foreground. A
|
||||
// newline is appended to format by default.
|
||||
func HiGreen(format string, a ...interface{}) { colorPrint(format, FgHiGreen, a...) }
|
||||
|
||||
// HiYellow is a convenient helper function to print with hi-intensity yellow foreground.
|
||||
// A newline is appended to format by default.
|
||||
func HiYellow(format string, a ...interface{}) { colorPrint(format, FgHiYellow, a...) }
|
||||
|
||||
// HiBlue is a convenient helper function to print with hi-intensity blue foreground. A
|
||||
// newline is appended to format by default.
|
||||
func HiBlue(format string, a ...interface{}) { colorPrint(format, FgHiBlue, a...) }
|
||||
|
||||
// HiMagenta is a convenient helper function to print with hi-intensity magenta foreground.
|
||||
// A newline is appended to format by default.
|
||||
func HiMagenta(format string, a ...interface{}) { colorPrint(format, FgHiMagenta, a...) }
|
||||
|
||||
// HiCyan is a convenient helper function to print with hi-intensity cyan foreground. A
|
||||
// newline is appended to format by default.
|
||||
func HiCyan(format string, a ...interface{}) { colorPrint(format, FgHiCyan, a...) }
|
||||
|
||||
// HiWhite is a convenient helper function to print with hi-intensity white foreground. A
|
||||
// newline is appended to format by default.
|
||||
func HiWhite(format string, a ...interface{}) { colorPrint(format, FgHiWhite, a...) }
|
||||
|
||||
// HiBlackString is a convenient helper function to return a string with hi-intensity black
|
||||
// foreground.
|
||||
func HiBlackString(format string, a ...interface{}) string {
|
||||
return colorString(format, FgHiBlack, a...)
|
||||
}
|
||||
|
||||
// HiRedString is a convenient helper function to return a string with hi-intensity red
|
||||
// foreground.
|
||||
func HiRedString(format string, a ...interface{}) string { return colorString(format, FgHiRed, a...) }
|
||||
|
||||
// HiGreenString is a convenient helper function to return a string with hi-intensity green
|
||||
// foreground.
|
||||
func HiGreenString(format string, a ...interface{}) string {
|
||||
return colorString(format, FgHiGreen, a...)
|
||||
}
|
||||
|
||||
// HiYellowString is a convenient helper function to return a string with hi-intensity yellow
|
||||
// foreground.
|
||||
func HiYellowString(format string, a ...interface{}) string {
|
||||
return colorString(format, FgHiYellow, a...)
|
||||
}
|
||||
|
||||
// HiBlueString is a convenient helper function to return a string with hi-intensity blue
|
||||
// foreground.
|
||||
func HiBlueString(format string, a ...interface{}) string { return colorString(format, FgHiBlue, a...) }
|
||||
|
||||
// HiMagentaString is a convenient helper function to return a string with hi-intensity magenta
|
||||
// foreground.
|
||||
func HiMagentaString(format string, a ...interface{}) string {
|
||||
return colorString(format, FgHiMagenta, a...)
|
||||
}
|
||||
|
||||
// HiCyanString is a convenient helper function to return a string with hi-intensity cyan
|
||||
// foreground.
|
||||
func HiCyanString(format string, a ...interface{}) string { return colorString(format, FgHiCyan, a...) }
|
||||
|
||||
// HiWhiteString is a convenient helper function to return a string with hi-intensity white
|
||||
// foreground.
|
||||
func HiWhiteString(format string, a ...interface{}) string {
|
||||
return colorString(format, FgHiWhite, a...)
|
||||
}
|
||||
133
vendor/github.com/fatih/color/doc.go
generated
vendored
133
vendor/github.com/fatih/color/doc.go
generated
vendored
|
|
@ -1,133 +0,0 @@
|
|||
/*
|
||||
Package color is an ANSI color package to output colorized or SGR defined
|
||||
output to the standard output. The API can be used in several way, pick one
|
||||
that suits you.
|
||||
|
||||
Use simple and default helper functions with predefined foreground colors:
|
||||
|
||||
color.Cyan("Prints text in cyan.")
|
||||
|
||||
// a newline will be appended automatically
|
||||
color.Blue("Prints %s in blue.", "text")
|
||||
|
||||
// More default foreground colors..
|
||||
color.Red("We have red")
|
||||
color.Yellow("Yellow color too!")
|
||||
color.Magenta("And many others ..")
|
||||
|
||||
// Hi-intensity colors
|
||||
color.HiGreen("Bright green color.")
|
||||
color.HiBlack("Bright black means gray..")
|
||||
color.HiWhite("Shiny white color!")
|
||||
|
||||
However there are times where custom color mixes are required. Below are some
|
||||
examples to create custom color objects and use the print functions of each
|
||||
separate color object.
|
||||
|
||||
// Create a new color object
|
||||
c := color.New(color.FgCyan).Add(color.Underline)
|
||||
c.Println("Prints cyan text with an underline.")
|
||||
|
||||
// Or just add them to New()
|
||||
d := color.New(color.FgCyan, color.Bold)
|
||||
d.Printf("This prints bold cyan %s\n", "too!.")
|
||||
|
||||
|
||||
// Mix up foreground and background colors, create new mixes!
|
||||
red := color.New(color.FgRed)
|
||||
|
||||
boldRed := red.Add(color.Bold)
|
||||
boldRed.Println("This will print text in bold red.")
|
||||
|
||||
whiteBackground := red.Add(color.BgWhite)
|
||||
whiteBackground.Println("Red text with White background.")
|
||||
|
||||
// Use your own io.Writer output
|
||||
color.New(color.FgBlue).Fprintln(myWriter, "blue color!")
|
||||
|
||||
blue := color.New(color.FgBlue)
|
||||
blue.Fprint(myWriter, "This will print text in blue.")
|
||||
|
||||
You can create PrintXxx functions to simplify even more:
|
||||
|
||||
// Create a custom print function for convenient
|
||||
red := color.New(color.FgRed).PrintfFunc()
|
||||
red("warning")
|
||||
red("error: %s", err)
|
||||
|
||||
// Mix up multiple attributes
|
||||
notice := color.New(color.Bold, color.FgGreen).PrintlnFunc()
|
||||
notice("don't forget this...")
|
||||
|
||||
You can also FprintXxx functions to pass your own io.Writer:
|
||||
|
||||
blue := color.New(FgBlue).FprintfFunc()
|
||||
blue(myWriter, "important notice: %s", stars)
|
||||
|
||||
// Mix up with multiple attributes
|
||||
success := color.New(color.Bold, color.FgGreen).FprintlnFunc()
|
||||
success(myWriter, don't forget this...")
|
||||
|
||||
|
||||
Or create SprintXxx functions to mix strings with other non-colorized strings:
|
||||
|
||||
yellow := New(FgYellow).SprintFunc()
|
||||
red := New(FgRed).SprintFunc()
|
||||
|
||||
fmt.Printf("this is a %s and this is %s.\n", yellow("warning"), red("error"))
|
||||
|
||||
info := New(FgWhite, BgGreen).SprintFunc()
|
||||
fmt.Printf("this %s rocks!\n", info("package"))
|
||||
|
||||
Windows support is enabled by default. All Print functions work as intended.
|
||||
However only for color.SprintXXX functions, user should use fmt.FprintXXX and
|
||||
set the output to color.Output:
|
||||
|
||||
fmt.Fprintf(color.Output, "Windows support: %s", color.GreenString("PASS"))
|
||||
|
||||
info := New(FgWhite, BgGreen).SprintFunc()
|
||||
fmt.Fprintf(color.Output, "this %s rocks!\n", info("package"))
|
||||
|
||||
Using with existing code is possible. Just use the Set() method to set the
|
||||
standard output to the given parameters. That way a rewrite of an existing
|
||||
code is not required.
|
||||
|
||||
// Use handy standard colors.
|
||||
color.Set(color.FgYellow)
|
||||
|
||||
fmt.Println("Existing text will be now in Yellow")
|
||||
fmt.Printf("This one %s\n", "too")
|
||||
|
||||
color.Unset() // don't forget to unset
|
||||
|
||||
// You can mix up parameters
|
||||
color.Set(color.FgMagenta, color.Bold)
|
||||
defer color.Unset() // use it in your function
|
||||
|
||||
fmt.Println("All text will be now bold magenta.")
|
||||
|
||||
There might be a case where you want to disable color output (for example to
|
||||
pipe the standard output of your app to somewhere else). `Color` has support to
|
||||
disable colors both globally and for single color definition. For example
|
||||
suppose you have a CLI app and a `--no-color` bool flag. You can easily disable
|
||||
the color output with:
|
||||
|
||||
var flagNoColor = flag.Bool("no-color", false, "Disable color output")
|
||||
|
||||
if *flagNoColor {
|
||||
color.NoColor = true // disables colorized output
|
||||
}
|
||||
|
||||
It also has support for single color definitions (local). You can
|
||||
disable/enable color output on the fly:
|
||||
|
||||
c := color.New(color.FgCyan)
|
||||
c.Println("Prints cyan text")
|
||||
|
||||
c.DisableColor()
|
||||
c.Println("This is printed without any color")
|
||||
|
||||
c.EnableColor()
|
||||
c.Println("This prints again cyan...")
|
||||
*/
|
||||
package color
|
||||
28
vendor/github.com/imdario/mergo/LICENSE
generated
vendored
28
vendor/github.com/imdario/mergo/LICENSE
generated
vendored
|
|
@ -1,28 +0,0 @@
|
|||
Copyright (c) 2013 Dario Castañé. All rights reserved.
|
||||
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
44
vendor/github.com/imdario/mergo/doc.go
generated
vendored
44
vendor/github.com/imdario/mergo/doc.go
generated
vendored
|
|
@ -1,44 +0,0 @@
|
|||
// Copyright 2013 Dario Castañé. All rights reserved.
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package mergo merges same-type structs and maps by setting default values in zero-value fields.
|
||||
|
||||
Mergo won't merge unexported (private) fields but will do recursively any exported one. It also won't merge structs inside maps (because they are not addressable using Go reflection).
|
||||
|
||||
Usage
|
||||
|
||||
From my own work-in-progress project:
|
||||
|
||||
type networkConfig struct {
|
||||
Protocol string
|
||||
Address string
|
||||
ServerType string `json: "server_type"`
|
||||
Port uint16
|
||||
}
|
||||
|
||||
type FssnConfig struct {
|
||||
Network networkConfig
|
||||
}
|
||||
|
||||
var fssnDefault = FssnConfig {
|
||||
networkConfig {
|
||||
"tcp",
|
||||
"127.0.0.1",
|
||||
"http",
|
||||
31560,
|
||||
},
|
||||
}
|
||||
|
||||
// Inside a function [...]
|
||||
|
||||
if err := mergo.Merge(&config, fssnDefault); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// More code [...]
|
||||
|
||||
*/
|
||||
package mergo
|
||||
174
vendor/github.com/imdario/mergo/map.go
generated
vendored
174
vendor/github.com/imdario/mergo/map.go
generated
vendored
|
|
@ -1,174 +0,0 @@
|
|||
// Copyright 2014 Dario Castañé. All rights reserved.
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Based on src/pkg/reflect/deepequal.go from official
|
||||
// golang's stdlib.
|
||||
|
||||
package mergo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
func changeInitialCase(s string, mapper func(rune) rune) string {
|
||||
if s == "" {
|
||||
return s
|
||||
}
|
||||
r, n := utf8.DecodeRuneInString(s)
|
||||
return string(mapper(r)) + s[n:]
|
||||
}
|
||||
|
||||
func isExported(field reflect.StructField) bool {
|
||||
r, _ := utf8.DecodeRuneInString(field.Name)
|
||||
return r >= 'A' && r <= 'Z'
|
||||
}
|
||||
|
||||
// Traverses recursively both values, assigning src's fields values to dst.
|
||||
// The map argument tracks comparisons that have already been seen, which allows
|
||||
// short circuiting on recursive types.
|
||||
func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *config) (err error) {
|
||||
overwrite := config.overwrite
|
||||
if dst.CanAddr() {
|
||||
addr := dst.UnsafeAddr()
|
||||
h := 17 * addr
|
||||
seen := visited[h]
|
||||
typ := dst.Type()
|
||||
for p := seen; p != nil; p = p.next {
|
||||
if p.ptr == addr && p.typ == typ {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// Remember, remember...
|
||||
visited[h] = &visit{addr, typ, seen}
|
||||
}
|
||||
zeroValue := reflect.Value{}
|
||||
switch dst.Kind() {
|
||||
case reflect.Map:
|
||||
dstMap := dst.Interface().(map[string]interface{})
|
||||
for i, n := 0, src.NumField(); i < n; i++ {
|
||||
srcType := src.Type()
|
||||
field := srcType.Field(i)
|
||||
if !isExported(field) {
|
||||
continue
|
||||
}
|
||||
fieldName := field.Name
|
||||
fieldName = changeInitialCase(fieldName, unicode.ToLower)
|
||||
if v, ok := dstMap[fieldName]; !ok || (isEmptyValue(reflect.ValueOf(v)) || overwrite) {
|
||||
dstMap[fieldName] = src.Field(i).Interface()
|
||||
}
|
||||
}
|
||||
case reflect.Ptr:
|
||||
if dst.IsNil() {
|
||||
v := reflect.New(dst.Type().Elem())
|
||||
dst.Set(v)
|
||||
}
|
||||
dst = dst.Elem()
|
||||
fallthrough
|
||||
case reflect.Struct:
|
||||
srcMap := src.Interface().(map[string]interface{})
|
||||
for key := range srcMap {
|
||||
srcValue := srcMap[key]
|
||||
fieldName := changeInitialCase(key, unicode.ToUpper)
|
||||
dstElement := dst.FieldByName(fieldName)
|
||||
if dstElement == zeroValue {
|
||||
// We discard it because the field doesn't exist.
|
||||
continue
|
||||
}
|
||||
srcElement := reflect.ValueOf(srcValue)
|
||||
dstKind := dstElement.Kind()
|
||||
srcKind := srcElement.Kind()
|
||||
if srcKind == reflect.Ptr && dstKind != reflect.Ptr {
|
||||
srcElement = srcElement.Elem()
|
||||
srcKind = reflect.TypeOf(srcElement.Interface()).Kind()
|
||||
} else if dstKind == reflect.Ptr {
|
||||
// Can this work? I guess it can't.
|
||||
if srcKind != reflect.Ptr && srcElement.CanAddr() {
|
||||
srcPtr := srcElement.Addr()
|
||||
srcElement = reflect.ValueOf(srcPtr)
|
||||
srcKind = reflect.Ptr
|
||||
}
|
||||
}
|
||||
|
||||
if !srcElement.IsValid() {
|
||||
continue
|
||||
}
|
||||
if srcKind == dstKind {
|
||||
if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
|
||||
return
|
||||
}
|
||||
} else if dstKind == reflect.Interface && dstElement.Kind() == reflect.Interface {
|
||||
if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
|
||||
return
|
||||
}
|
||||
} else if srcKind == reflect.Map {
|
||||
if err = deepMap(dstElement, srcElement, visited, depth+1, config); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("type mismatch on %s field: found %v, expected %v", fieldName, srcKind, dstKind)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Map sets fields' values in dst from src.
|
||||
// src can be a map with string keys or a struct. dst must be the opposite:
|
||||
// if src is a map, dst must be a valid pointer to struct. If src is a struct,
|
||||
// dst must be map[string]interface{}.
|
||||
// It won't merge unexported (private) fields and will do recursively
|
||||
// any exported field.
|
||||
// If dst is a map, keys will be src fields' names in lower camel case.
|
||||
// Missing key in src that doesn't match a field in dst will be skipped. This
|
||||
// doesn't apply if dst is a map.
|
||||
// This is separated method from Merge because it is cleaner and it keeps sane
|
||||
// semantics: merging equal types, mapping different (restricted) types.
|
||||
func Map(dst, src interface{}, opts ...func(*config)) error {
|
||||
return _map(dst, src, opts...)
|
||||
}
|
||||
|
||||
// MapWithOverwrite will do the same as Map except that non-empty dst attributes will be overriden by
|
||||
// non-empty src attribute values.
|
||||
// Deprecated: Use Map(…) with WithOverride
|
||||
func MapWithOverwrite(dst, src interface{}, opts ...func(*config)) error {
|
||||
return _map(dst, src, append(opts, WithOverride)...)
|
||||
}
|
||||
|
||||
func _map(dst, src interface{}, opts ...func(*config)) error {
|
||||
var (
|
||||
vDst, vSrc reflect.Value
|
||||
err error
|
||||
)
|
||||
config := &config{}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(config)
|
||||
}
|
||||
|
||||
if vDst, vSrc, err = resolveValues(dst, src); err != nil {
|
||||
return err
|
||||
}
|
||||
// To be friction-less, we redirect equal-type arguments
|
||||
// to deepMerge. Only because arguments can be anything.
|
||||
if vSrc.Kind() == vDst.Kind() {
|
||||
return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config)
|
||||
}
|
||||
switch vSrc.Kind() {
|
||||
case reflect.Struct:
|
||||
if vDst.Kind() != reflect.Map {
|
||||
return ErrExpectedMapAsDestination
|
||||
}
|
||||
case reflect.Map:
|
||||
if vDst.Kind() != reflect.Struct {
|
||||
return ErrExpectedStructAsDestination
|
||||
}
|
||||
default:
|
||||
return ErrNotSupported
|
||||
}
|
||||
return deepMap(vDst, vSrc, make(map[uintptr]*visit), 0, config)
|
||||
}
|
||||
219
vendor/github.com/imdario/mergo/merge.go
generated
vendored
219
vendor/github.com/imdario/mergo/merge.go
generated
vendored
|
|
@ -1,219 +0,0 @@
|
|||
// Copyright 2013 Dario Castañé. All rights reserved.
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Based on src/pkg/reflect/deepequal.go from official
|
||||
// golang's stdlib.
|
||||
|
||||
package mergo
|
||||
|
||||
import "reflect"
|
||||
|
||||
func hasExportedField(dst reflect.Value) (exported bool) {
|
||||
for i, n := 0, dst.NumField(); i < n; i++ {
|
||||
field := dst.Type().Field(i)
|
||||
if field.Anonymous && dst.Field(i).Kind() == reflect.Struct {
|
||||
exported = exported || hasExportedField(dst.Field(i))
|
||||
} else {
|
||||
exported = exported || len(field.PkgPath) == 0
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type config struct {
|
||||
overwrite bool
|
||||
transformers transformers
|
||||
}
|
||||
|
||||
type transformers interface {
|
||||
Transformer(reflect.Type) func(dst, src reflect.Value) error
|
||||
}
|
||||
|
||||
// Traverses recursively both values, assigning src's fields values to dst.
|
||||
// The map argument tracks comparisons that have already been seen, which allows
|
||||
// short circuiting on recursive types.
|
||||
func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *config) (err error) {
|
||||
overwrite := config.overwrite
|
||||
|
||||
if !src.IsValid() {
|
||||
return
|
||||
}
|
||||
if dst.CanAddr() {
|
||||
addr := dst.UnsafeAddr()
|
||||
h := 17 * addr
|
||||
seen := visited[h]
|
||||
typ := dst.Type()
|
||||
for p := seen; p != nil; p = p.next {
|
||||
if p.ptr == addr && p.typ == typ {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// Remember, remember...
|
||||
visited[h] = &visit{addr, typ, seen}
|
||||
}
|
||||
|
||||
if config.transformers != nil && !isEmptyValue(dst) {
|
||||
if fn := config.transformers.Transformer(dst.Type()); fn != nil {
|
||||
err = fn(dst, src)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
switch dst.Kind() {
|
||||
case reflect.Struct:
|
||||
if hasExportedField(dst) {
|
||||
for i, n := 0, dst.NumField(); i < n; i++ {
|
||||
if err = deepMerge(dst.Field(i), src.Field(i), visited, depth+1, config); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if dst.CanSet() && !isEmptyValue(src) && (overwrite || isEmptyValue(dst)) {
|
||||
dst.Set(src)
|
||||
}
|
||||
}
|
||||
case reflect.Map:
|
||||
if len(src.MapKeys()) == 0 && !src.IsNil() && len(dst.MapKeys()) == 0 {
|
||||
dst.Set(reflect.MakeMap(dst.Type()))
|
||||
return
|
||||
}
|
||||
for _, key := range src.MapKeys() {
|
||||
srcElement := src.MapIndex(key)
|
||||
if !srcElement.IsValid() {
|
||||
continue
|
||||
}
|
||||
dstElement := dst.MapIndex(key)
|
||||
switch srcElement.Kind() {
|
||||
case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Slice:
|
||||
if srcElement.IsNil() {
|
||||
continue
|
||||
}
|
||||
fallthrough
|
||||
default:
|
||||
if !srcElement.CanInterface() {
|
||||
continue
|
||||
}
|
||||
switch reflect.TypeOf(srcElement.Interface()).Kind() {
|
||||
case reflect.Struct:
|
||||
fallthrough
|
||||
case reflect.Ptr:
|
||||
fallthrough
|
||||
case reflect.Map:
|
||||
if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
|
||||
return
|
||||
}
|
||||
case reflect.Slice:
|
||||
srcSlice := reflect.ValueOf(srcElement.Interface())
|
||||
|
||||
var dstSlice reflect.Value
|
||||
if !dstElement.IsValid() || dstElement.IsNil() {
|
||||
dstSlice = reflect.MakeSlice(srcSlice.Type(), 0, srcSlice.Len())
|
||||
} else {
|
||||
dstSlice = reflect.ValueOf(dstElement.Interface())
|
||||
}
|
||||
|
||||
dstSlice = reflect.AppendSlice(dstSlice, srcSlice)
|
||||
dst.SetMapIndex(key, dstSlice)
|
||||
}
|
||||
}
|
||||
if dstElement.IsValid() && reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Map {
|
||||
continue
|
||||
}
|
||||
|
||||
if !isEmptyValue(srcElement) && (overwrite || (!dstElement.IsValid() || isEmptyValue(dst))) {
|
||||
if dst.IsNil() {
|
||||
dst.Set(reflect.MakeMap(dst.Type()))
|
||||
}
|
||||
dst.SetMapIndex(key, srcElement)
|
||||
}
|
||||
}
|
||||
case reflect.Slice:
|
||||
dst.Set(reflect.AppendSlice(dst, src))
|
||||
case reflect.Ptr:
|
||||
fallthrough
|
||||
case reflect.Interface:
|
||||
if src.IsNil() {
|
||||
break
|
||||
}
|
||||
if src.Kind() != reflect.Interface {
|
||||
if dst.IsNil() || overwrite {
|
||||
if dst.CanSet() && (overwrite || isEmptyValue(dst)) {
|
||||
dst.Set(src)
|
||||
}
|
||||
} else if src.Kind() == reflect.Ptr {
|
||||
if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil {
|
||||
return
|
||||
}
|
||||
} else if dst.Elem().Type() == src.Type() {
|
||||
if err = deepMerge(dst.Elem(), src, visited, depth+1, config); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
return ErrDifferentArgumentsTypes
|
||||
}
|
||||
break
|
||||
}
|
||||
if dst.IsNil() || overwrite {
|
||||
if dst.CanSet() && (overwrite || isEmptyValue(dst)) {
|
||||
dst.Set(src)
|
||||
}
|
||||
} else if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil {
|
||||
return
|
||||
}
|
||||
default:
|
||||
if dst.CanSet() && !isEmptyValue(src) && (overwrite || isEmptyValue(dst)) {
|
||||
dst.Set(src)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Merge will fill any empty for value type attributes on the dst struct using corresponding
|
||||
// src attributes if they themselves are not empty. dst and src must be valid same-type structs
|
||||
// and dst must be a pointer to struct.
|
||||
// It won't merge unexported (private) fields and will do recursively any exported field.
|
||||
func Merge(dst, src interface{}, opts ...func(*config)) error {
|
||||
return merge(dst, src, opts...)
|
||||
}
|
||||
|
||||
// MergeWithOverwrite will do the same as Merge except that non-empty dst attributes will be overriden by
|
||||
// non-empty src attribute values.
|
||||
// Deprecated: use Merge(…) with WithOverride
|
||||
func MergeWithOverwrite(dst, src interface{}, opts ...func(*config)) error {
|
||||
return merge(dst, src, append(opts, WithOverride)...)
|
||||
}
|
||||
|
||||
// WithTransformers adds transformers to merge, allowing to customize the merging of some types.
|
||||
func WithTransformers(transformers transformers) func(*config) {
|
||||
return func(config *config) {
|
||||
config.transformers = transformers
|
||||
}
|
||||
}
|
||||
|
||||
// WithOverride will make merge override non-empty dst attributes with non-empty src attributes values.
|
||||
func WithOverride(config *config) {
|
||||
config.overwrite = true
|
||||
}
|
||||
|
||||
func merge(dst, src interface{}, opts ...func(*config)) error {
|
||||
var (
|
||||
vDst, vSrc reflect.Value
|
||||
err error
|
||||
)
|
||||
|
||||
config := &config{}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(config)
|
||||
}
|
||||
|
||||
if vDst, vSrc, err = resolveValues(dst, src); err != nil {
|
||||
return err
|
||||
}
|
||||
if vDst.Type() != vSrc.Type() {
|
||||
return ErrDifferentArgumentsTypes
|
||||
}
|
||||
return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config)
|
||||
}
|
||||
92
vendor/github.com/imdario/mergo/mergo.go
generated
vendored
92
vendor/github.com/imdario/mergo/mergo.go
generated
vendored
|
|
@ -1,92 +0,0 @@
|
|||
// Copyright 2013 Dario Castañé. All rights reserved.
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Based on src/pkg/reflect/deepequal.go from official
|
||||
// golang's stdlib.
|
||||
|
||||
package mergo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Errors reported by Mergo when it finds invalid arguments.
|
||||
var (
|
||||
ErrNilArguments = errors.New("src and dst must not be nil")
|
||||
ErrDifferentArgumentsTypes = errors.New("src and dst must be of same type")
|
||||
ErrNotSupported = errors.New("only structs and maps are supported")
|
||||
ErrExpectedMapAsDestination = errors.New("dst was expected to be a map")
|
||||
ErrExpectedStructAsDestination = errors.New("dst was expected to be a struct")
|
||||
)
|
||||
|
||||
// During deepMerge, must keep track of checks that are
|
||||
// in progress. The comparison algorithm assumes that all
|
||||
// checks in progress are true when it reencounters them.
|
||||
// Visited are stored in a map indexed by 17 * a1 + a2;
|
||||
type visit struct {
|
||||
ptr uintptr
|
||||
typ reflect.Type
|
||||
next *visit
|
||||
}
|
||||
|
||||
// From src/pkg/encoding/json/encode.go.
|
||||
func isEmptyValue(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||
return v.Len() == 0
|
||||
case reflect.Bool:
|
||||
return !v.Bool()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return v.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return v.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float() == 0
|
||||
case reflect.Interface, reflect.Ptr, reflect.Func:
|
||||
return v.IsNil()
|
||||
case reflect.Invalid:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func resolveValues(dst, src interface{}) (vDst, vSrc reflect.Value, err error) {
|
||||
if dst == nil || src == nil {
|
||||
err = ErrNilArguments
|
||||
return
|
||||
}
|
||||
vDst = reflect.ValueOf(dst).Elem()
|
||||
if vDst.Kind() != reflect.Struct && vDst.Kind() != reflect.Map {
|
||||
err = ErrNotSupported
|
||||
return
|
||||
}
|
||||
vSrc = reflect.ValueOf(src)
|
||||
// We check if vSrc is a pointer to dereference it.
|
||||
if vSrc.Kind() == reflect.Ptr {
|
||||
vSrc = vSrc.Elem()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Traverses recursively both values, assigning src's fields values to dst.
|
||||
// The map argument tracks comparisons that have already been seen, which allows
|
||||
// short circuiting on recursive types.
|
||||
func deeper(dst, src reflect.Value, visited map[uintptr]*visit, depth int) (err error) {
|
||||
if dst.CanAddr() {
|
||||
addr := dst.UnsafeAddr()
|
||||
h := 17 * addr
|
||||
seen := visited[h]
|
||||
typ := dst.Type()
|
||||
for p := seen; p != nil; p = p.next {
|
||||
if p.ptr == addr && p.typ == typ {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// Remember, remember...
|
||||
visited[h] = &visit{addr, typ, seen}
|
||||
}
|
||||
return // TODO refactor
|
||||
}
|
||||
4
vendor/github.com/imdario/mergo/testdata/license.yml
generated
vendored
4
vendor/github.com/imdario/mergo/testdata/license.yml
generated
vendored
|
|
@ -1,4 +0,0 @@
|
|||
import: ../../../../fossene/db/schema/thing.yml
|
||||
fields:
|
||||
site: string
|
||||
author: root
|
||||
21
vendor/github.com/mattn/go-colorable/LICENSE
generated
vendored
21
vendor/github.com/mattn/go-colorable/LICENSE
generated
vendored
|
|
@ -1,21 +0,0 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Yasuhiro Matsumoto
|
||||
|
||||
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.
|
||||
29
vendor/github.com/mattn/go-colorable/colorable_appengine.go
generated
vendored
29
vendor/github.com/mattn/go-colorable/colorable_appengine.go
generated
vendored
|
|
@ -1,29 +0,0 @@
|
|||
// +build appengine
|
||||
|
||||
package colorable
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
_ "github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
// NewColorable return new instance of Writer which handle escape sequence.
|
||||
func NewColorable(file *os.File) io.Writer {
|
||||
if file == nil {
|
||||
panic("nil passed instead of *os.File to NewColorable()")
|
||||
}
|
||||
|
||||
return file
|
||||
}
|
||||
|
||||
// NewColorableStdout return new instance of Writer which handle escape sequence for stdout.
|
||||
func NewColorableStdout() io.Writer {
|
||||
return os.Stdout
|
||||
}
|
||||
|
||||
// NewColorableStderr return new instance of Writer which handle escape sequence for stderr.
|
||||
func NewColorableStderr() io.Writer {
|
||||
return os.Stderr
|
||||
}
|
||||
30
vendor/github.com/mattn/go-colorable/colorable_others.go
generated
vendored
30
vendor/github.com/mattn/go-colorable/colorable_others.go
generated
vendored
|
|
@ -1,30 +0,0 @@
|
|||
// +build !windows
|
||||
// +build !appengine
|
||||
|
||||
package colorable
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
_ "github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
// NewColorable return new instance of Writer which handle escape sequence.
|
||||
func NewColorable(file *os.File) io.Writer {
|
||||
if file == nil {
|
||||
panic("nil passed instead of *os.File to NewColorable()")
|
||||
}
|
||||
|
||||
return file
|
||||
}
|
||||
|
||||
// NewColorableStdout return new instance of Writer which handle escape sequence for stdout.
|
||||
func NewColorableStdout() io.Writer {
|
||||
return os.Stdout
|
||||
}
|
||||
|
||||
// NewColorableStderr return new instance of Writer which handle escape sequence for stderr.
|
||||
func NewColorableStderr() io.Writer {
|
||||
return os.Stderr
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue