Compare commits

..

110 Commits

Author SHA1 Message Date
Andrey Nering
bc99509395 Merge branch 'master' into v3 2019-09-22 18:43:45 -03:00
Andrey Nering
5c420f3a34 v2.7.0 2019-09-22 18:21:31 -03:00
Andrey Nering
d3060b0060 Add CHANGELOG for #216 2019-09-14 18:09:34 -03:00
Andrey Nering
14d7f04a81 Always expode .TIMESTAMP and .STATUS when using status: 2019-09-14 18:04:41 -03:00
Andrey Nering
1a28e5e0d4 Few code improvements on #216 2019-09-14 17:54:41 -03:00
Andrey Nering
884cd0d636 Merge branch 'CypherpunkArmory-report-timestamp-to-status' into v3 2019-09-14 17:18:42 -03:00
Andrey Nering
6a7a3c0ae8 Merge pull request #246 from go-task/method-on-v3
Add global "method:" option to allow setting a default for all tasks. Change default from "timestamp" to "checksum"
2019-09-08 23:02:40 -03:00
Andrey Nering
948e6bd57c Update v3 CHANGELOG 2019-09-08 22:59:27 -03:00
Andrey Nering
78595fba0b Make "checksum" the default method in v3 2019-09-08 22:51:56 -03:00
Andrey Nering
8020284b12 Add global method: option to set default method 2019-09-08 22:51:14 -03:00
Andrey Nering
d6a49da870 Merge branch 'master' into v3 2019-09-08 22:12:02 -03:00
Andrey Nering
84da80356d Use stdlib instead of go-homedir 2019-09-08 22:07:48 -03:00
Andrey Nering
bcbb85eac3 Merge pull request #245 from go-task/upgrade-sh
Upgrade mvdan.cc/sh to use edge
2019-09-08 21:59:59 -03:00
Andrey Nering
0e1d8a72e6 Revert "Ensure the $HOME env is being set on Windows"
This reverts commit 52028fc3bc.
2019-09-08 21:56:23 -03:00
Andrey Nering
bbdd698869 Upgrade mvdan.cc/sh to use edge 2019-09-08 21:55:02 -03:00
Andrey Nering
bae1e1ee9f Update .gitignore 2019-09-07 14:46:46 -03:00
Andrey Nering
7138785500 Merge branch 'master' into v3 2019-09-07 14:44:21 -03:00
Andrey Nering
8f684ffa6d Merge pull request #239 from go-task/ci-using-github-actions
Add CI for Linux/Windows/MacOS powered by GitHub Actions
2019-09-07 14:43:00 -03:00
Andrey Nering
9be3666fe7 Trying to fix Travis 2019-09-07 14:40:39 -03:00
Andrey Nering
b7785678f4 CI: Upgrade to Go 1.13 2019-09-07 14:35:48 -03:00
Andrey Nering
d8005b4cf6 Fix typo 2019-09-07 14:32:02 -03:00
Andrey Nering
52028fc3bc Ensure the $HOME env is being set on Windows 2019-09-07 14:29:13 -03:00
Andrey Nering
5285ec23ae Fix summary test on Windows 2019-09-01 22:26:53 -03:00
Andrey Nering
3c882e5c57 Merge branch 'master' into ci-using-github-actions 2019-09-01 22:18:58 -03:00
Andrey Nering
1a33f9168b Merge branch 'report-timestamp-to-status' of https://github.com/CypherpunkArmory/task into CypherpunkArmory-report-timestamp-to-status 2019-09-01 21:44:23 -03:00
Andrey Nering
ccae3d7383 Update CHANGELOG 2019-08-25 18:17:33 -03:00
Andrey Nering
847651a90a Improve note wording a bit 2019-08-25 18:15:26 -03:00
Andrey Nering
1b8998e7a2 Merge pull request #237 from jaedle/v3
Remove all code related support of version 1
2019-08-25 18:11:47 -03:00
Andrey Nering
dc8fb79759 Merge branch 'master' into v3 2019-08-25 18:05:40 -03:00
Stephen Prater
6b0935d6cf Fix tests 2019-08-25 13:47:29 -07:00
Stephen Prater
d1183ce272 Merge branch 'master' into report-timestamp-to-status
* master:
  Update CHANGELOG
  Small improvements to #228
  Fix a typo
  Fix Checksum.IsUpToDate
  Remove directory check
  Update glob.go
  Separate error handlings for readability
  Re-run the task if generated files do not exist
2019-08-25 13:46:02 -07:00
Stephen Prater
a1aec8178a Export Time Struct to Template 2019-08-25 13:36:48 -07:00
Andrey Nering
ad569a8a36 Update CHANGELOG 2019-08-25 16:41:54 -03:00
Andrey Nering
0d9fdbaac1 Small improvements to #228 2019-08-25 16:32:25 -03:00
Andrey Nering
f5cd3eab9e Merge pull request #228 from topecongiro/checksum-look-for-generated
Re-run the task if generated files do not exist
2019-08-25 16:32:03 -03:00
Stephen Prater
cb6fe4bb59 Merge remote-tracking branch 'upstream/v3' into report-timestamp-to-status
* upstream/v3:
  v3.0.0-preview1
  Update v3 changelog
  Only have colored output on v3
  Add --color=false flag to disable colored output
  Update documentation about sprig
  Update CHANGELOG
  Migrate from sprig to slim-sprig
  Fix build after merging master
  Use colors for some output messages
2019-08-25 10:33:13 -07:00
Stephen Prater
db36bc67f1 Changes per feedback 2019-08-25 10:30:00 -07:00
Stephen Prater
e0f72a6193 Apply suggestions from code review
Co-Authored-By: Andrey Nering <andrey.nering@gmail.com>
2019-08-25 09:39:39 -07:00
Stephen Prater
1ee684b7c0 Expose timestamp and checksum to status 2019-08-25 09:39:39 -07:00
jaedle
93005512b4 cleanp taskfile reader 2019-08-24 06:28:12 +02:00
Seiichi Uchida
8987cd64a0 Fix a typo 2019-08-21 13:42:19 +09:00
Seiichi Uchida
fac51dcf03 Fix Checksum.IsUpToDate
- Check whether generates exist after the creation of checksum file
- Check whether generates exist if only the user specified generates fields
- Check for each generates field instead of taking it as a whole
2019-08-21 13:35:16 +09:00
Seiichi Uchida
01101a4c9b Remove directory check 2019-08-21 13:34:58 +09:00
Seiichi Uchida
d561e40817 Update glob.go
- Rename glob() to globs()
- Add glob() which handles a single glob pattern
- Change glob() and globs() so that they do not return directoreis
2019-08-21 13:33:12 +09:00
jaedle
b8094fd771 bump version of auto generated taskfile 2019-08-19 21:01:36 +02:00
jaedle
af5d9c952d assert error message 2019-08-19 21:01:01 +02:00
jaedle
ce4e187cbc show documentation of version 1, add deprecation notice 2019-08-19 20:59:02 +02:00
jaedle
821c80b61e Fix error message
Co-Authored-By: Andrey Nering <andrey.nering@gmail.com>
2019-08-19 20:53:35 +02:00
jaedle
5a6fb7c973 Bump version of taskfile
Co-Authored-By: Andrey Nering <andrey.nering@gmail.com>
2019-08-19 20:53:20 +02:00
Seiichi Uchida
0cb298ebdf Separate error handlings for readability 2019-08-19 13:00:10 +09:00
jaedle
0f385f9f4e remove v1 2019-08-18 17:37:21 +02:00
Andrey Nering
a149368725 Calling Go directly on Windows 2019-08-15 22:50:28 -03:00
Andrey Nering
afeefe8259 Build on the root directory 2019-08-15 22:45:46 -03:00
Andrey Nering
690d3c27a2 Do we need to use backslash here? 2019-08-15 22:42:06 -03:00
Andrey Nering
3d56ea5ce5 Fix binary calling on Windows 2019-08-15 22:38:36 -03:00
Andrey Nering
fdff7f80a3 Skip linting for now 2019-08-15 22:35:29 -03:00
Andrey Nering
fe6978b107 Fix CI 2019-08-15 22:32:45 -03:00
Andrey Nering
57db6865d2 Build binary first and test all packages 2019-08-15 22:28:55 -03:00
Andrey Nering
d235d5ab28 Add CI for Linux/Windows/MacOS powered by GitHub Actions 2019-08-15 22:24:31 -03:00
Andrey Nering
c47c15ee47 v3.0.0-preview1 2019-08-11 23:43:27 -03:00
Andrey Nering
613dfe06d3 Fix two Goreleaser deprecation messages
https://goreleaser.com/deprecations/#archive

https://goreleaser.com/deprecations/#nfpm
2019-08-11 23:15:08 -03:00
Andrey Nering
6803ad2e59 Update v3 changelog 2019-08-11 23:10:12 -03:00
Andrey Nering
d5a791b470 Merge branch 'master' into v3 2019-08-11 23:03:58 -03:00
Andrey Nering
a312d61d68 Update CHANGELOG.md 2019-08-11 23:01:25 -03:00
Andrey Nering
e414c1f7b0 Documentation: Improve wording on the Variables section
Thanks @crewjam for the suggestion

Closes #234
2019-08-10 19:50:47 -03:00
Andrey Nering
955359b073 Fix nil panic bug when assigning global var and no var is declared on the Taskfile
Closes #229
Ref #234
2019-08-10 19:38:57 -03:00
Seiichi Uchida
26e0c0887a Re-run the task if generated files do not exist 2019-08-01 13:08:53 +09:00
Andrey Nering
4c295b564a Update CHANGELOG.md 2019-07-21 11:23:49 -03:00
Andrey Nering
2eb52da0db v2.6.0 2019-07-21 11:11:30 -03:00
Andrey Nering
d8bfb3ab13 Add CHANGELOG and documentation for Linux support on Homebrew
Ref: https://github.com/go-task/homebrew-tap/pull/1

Thanks @dawidd6
2019-07-21 11:03:22 -03:00
Andrey Nering
d970e93507 Add --taskfile flag (alias -t) to allow running another Taskfile
Closes #221
2019-07-21 10:57:04 -03:00
Andrey Nering
e6255081a8 Merge pull request #207 from go-task/colored-output
Use colors for some output messages
2019-07-07 14:41:25 -03:00
Andrey Nering
623db0ed94 Only have colored output on v3 2019-07-07 14:18:02 -03:00
Andrey Nering
0e575e9c25 Add --color=false flag to disable colored output 2019-07-07 14:13:53 -03:00
Andrey Nering
fb23ba9878 Merge branch 'v3' into colored-output 2019-07-07 14:04:12 -03:00
Andrey Nering
4e09fc7f43 Update documentation about sprig 2019-06-22 22:48:53 -03:00
Andrey Nering
64cfdd815f Update CHANGELOG 2019-06-22 22:47:23 -03:00
Andrey Nering
f6f31e0a8d Merge pull request #219 from go-task/migrate-from-sprig-to-slim-sprig
Migrate from sprig to slim-sprig
2019-06-22 22:37:42 -03:00
Andrey Nering
bd5fb9be03 Migrate from sprig to slim-sprig 2019-06-22 22:17:24 -03:00
Andrey Nering
762714de68 Merge pull request #213 from marco-m/document-dir-creation
Document dir: creation (see PR #211)
2019-06-16 11:03:08 -03:00
Marco Molteni
82a3651a18 Document dir: creation (see PR #211) 2019-06-16 11:33:00 +02:00
Andrey Nering
dd9cdb0ec9 Fix build after merging master 2019-06-15 22:47:15 -03:00
Andrey Nering
7f082a821d Merge branch 'v3' into colored-output 2019-06-15 22:42:15 -03:00
Andrey Nering
abe0352de9 Fixed some bugs regarding minor version checks on version:
1. I have forgot to update it on recent releases. Seems that most people just
   use round versions since nobody complained.
2. It's too hard to understand how the github.com/Masterminds/semver package
   works, so I just got rid of it and we're now using plain float checks.
2019-06-15 22:39:35 -03:00
Andrey Nering
4cee4aa5a8 Fix typo 2019-06-15 21:58:37 -03:00
Andrey Nering
9c68c7c50b Add changelog for #205 2019-06-15 21:56:34 -03:00
Andrey Nering
0608782cfa Merge pull request #205 from CypherpunkArmory/add-precondition-to-task
Add Preconditions to Tasks
2019-06-15 21:55:20 -03:00
Andrey Nering
edeaf3794a Merge pull request #212 from ezhukov/patch-1
Add missing "-" in usage.md
2019-06-15 21:21:41 -03:00
Andrey Nering
fe2b8c8afa Post-fixes to #211 2019-06-15 21:12:54 -03:00
Andrey Nering
b66bf58064 Merge branch 'marco-m-209-create-dir' 2019-06-15 20:53:34 -03:00
Andrey Nering
957dfa9cdf Merge branch '209-create-dir' of https://github.com/marco-m/task into marco-m-209-create-dir 2019-06-15 20:52:08 -03:00
Stephen Prater
cc9264854e Change error output 2019-06-11 12:20:56 -07:00
Stephen Prater
d1463b3e24 Fix typos per review 2019-06-11 11:46:22 -07:00
Eugene Zhukov
f1082520e1 Add missing "-" in usage.md 2019-06-11 11:35:10 +03:00
Marco Molteni
733c563194 Protect creation of "dir:" with a mutex 2019-06-10 17:40:20 +02:00
Andrey Nering
0200d043c3 Add funding button via OpenCollective 2019-06-09 21:53:55 -03:00
Marco Molteni
9c475c36e7 Handle the common case when the task directory is not specified
Closes #209
2019-06-06 20:40:31 +02:00
Marco Molteni
c663c5c507 When "dir:" attribute points to a non-existing dir, create it
Closes #209
2019-06-04 18:58:22 +02:00
Marco Molteni
1e93c38307 Task directory: test when "dir:" attribute points to an existing dir 2019-06-04 18:36:35 +02:00
Marco Molteni
81baf808c9 Task directory: test default case (no "dir:" attribute) 2019-06-04 18:24:01 +02:00
Marco Molteni
74537689dc Fix spelling 2019-06-04 08:08:25 +02:00
Stephen Prater
12ab01d5e6 Clarify difference between status and precondition in docs 2019-05-28 13:18:06 -07:00
Stephen Prater
044d3a0ff9 Remove ignore_errors 2019-05-28 13:02:59 -07:00
Stephen Prater
659cae6a4c Apply suggestions from code review
Co-Authored-By: Andrey Nering <andrey.nering@gmail.com>
2019-05-28 12:28:29 -07:00
Andrey Nering
8efc38ad82 Use colors for some output messages 2019-05-26 18:36:39 -03:00
Stephen Prater
bd5882f0f0 Add Preconditions to Tasks 2019-05-17 13:51:15 -07:00
Andrey Nering
6ff9ba9df9 v2.5.2 2019-05-11 11:28:21 -03:00
Andrey Nering
b2df398a12 go mod vendor 2019-05-11 11:22:47 -03:00
Andrey Nering
83d618e1eb Revert "Upgrade to yaml/go-yaml v3"
This reverts commit 8001fb3915.
2019-05-11 11:22:13 -03:00
Andrey Nering
f0768b3af1 Allow setting global variables through the CLI
Closes #192
2019-05-11 11:06:47 -03:00
88 changed files with 1966 additions and 2523 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
open_collective: task

59
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,59 @@
name: Test
on: [push]
jobs:
linux:
name: Linux
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.13
uses: actions/setup-go@v1
with:
go-version: 1.13
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v1
- name: Build
run: go build -o ./task -v ./cmd/task
- name: Test
run: ./task test
windows:
name: Windows
runs-on: windows-latest
steps:
- name: Set up Go 1.13
uses: actions/setup-go@v1
with:
go-version: 1.13
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v1
- name: Build
run: go install -v ./cmd/task
- name: Test
run: go test -v ./...
macos:
name: MacOS
runs-on: macOS-latest
steps:
- name: Set up Go 1.13
uses: actions/setup-go@v1
with:
go-version: 1.13
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v1
- name: Build
run: go build -o ./task -v ./cmd/task
- name: Test
run: ./task test

6
.gitignore vendored
View File

@@ -21,3 +21,9 @@ dist/
# intellij idea/goland
.idea/
# exuberant ctags
tags
/bin
/testdata/vars/v1

View File

@@ -14,12 +14,11 @@ build:
env:
- CGO_ENABLED=0
archive:
name_template: "{{.Binary}}_{{.Os}}_{{.Arch}}"
format_overrides:
- goos: windows
format: zip
archives:
- name_template: "{{.Binary}}_{{.Os}}_{{.Arch}}"
format_overrides:
- goos: windows
format: zip
release:
draft: true
@@ -30,15 +29,15 @@ snapshot:
checksum:
name_template: "task_checksums.txt"
nfpm:
vendor: Task
homepage: https://github.com/go-task/task
maintainer: Andrey Nering <andrey.nering@gmail.com>
description: Simple task runner written in Go
license: MIT
conflicts:
- taskwarrior
formats:
- deb
- rpm
name_template: "{{.ProjectName}}_{{.Os}}_{{.Arch}}"
nfpms:
- vendor: Task
homepage: https://github.com/go-task/task
maintainer: Andrey Nering <andrey.nering@gmail.com>
description: Simple task runner written in Go
license: MIT
conflicts:
- taskwarrior
formats:
- deb
- rpm
name_template: "{{.ProjectName}}_{{.Os}}_{{.Arch}}"

View File

@@ -1,8 +1,8 @@
language: go
go:
- 1.11.x
- 1.12.x
- 1.13.x
addons:
apt:
@@ -10,7 +10,7 @@ addons:
- rpm
script:
- go install github.com/go-task/task/cmd/task
- go install -v ./cmd/task
- task ci
deploy:

View File

@@ -1,5 +1,59 @@
# Changelog
# v3.0.0 - Preview 2
- Taskfiles in version 1 are not supported anymore
([#237](https://github.com/go-task/task/pull/237)).
- Added global `method:` option. With this option, you can set a default
method to all tasks in a Taskfile
([#246](https://github.com/go-task/task/issues/246)).
- Changed default method from `timestamp` to `checksum`
([#246](https://github.com/go-task/task/issues/246)).
- New magic variables are now available when using `status:`:
`.TIMESTAMP` which contains the greatest modification date
from the files listed in `sources:`, and `.CHECKSUM`, which
contains a checksum of all files listed in `status:`.
This is useful for manual checking when using external, or even remote,
artifacts when using `status:`
([#216](https://github.com/go-task/task/pull/216)).
## v3.0.0 - Preview 1
- We're now using [slim-sprig](https://github.com/go-task/slim-sprig) instead of
[sprig](https://github.com/Masterminds/sprig), which allowed a file size
reduction of about 22%
([#219](https://github.com/go-task/task/pull/219)).
- We now use some colors on Task output to better distinguish message types -
commands are green, errors are red, etc
([#207](https://github.com/go-task/task/pull/207)).
## v2.7.0 - 2019-09-22
- Fixed panic bug when assigning a global variable
([#229](https://github.com/go-task/task/issues/229), [#243](https://github.com/go-task/task/issues/234)).
- A task with `method: checksum` will now re-run if generated files are deleted
([#228](https://github.com/go-task/task/pull/228), [#238](https://github.com/go-task/task/issues/238)).
## v2.6.0 - 2019-07-21
- Fixed some bugs regarding minor version checks on `version:`.
- Add `preconditions:` to task
([#205](https://github.com/go-task/task/pull/205)).
- Create directory informed on `dir:` if it doesn't exist
([#209](https://github.com/go-task/task/issues/209), [#211](https://github.com/go-task/task/pull/211)).
- We now have a `--taskfile` flag (alias `-t`), which can be used to run
another Taskfile (other than the default `Taskfile.yml`)
([#221](https://github.com/go-task/task/pull/221)).
- It's now possible to install Task using Homebrew on Linux
([go-task/homebrew-tap#1](https://github.com/go-task/homebrew-tap/pull/1)).
## v2.5.2 - 2019-05-11
- Reverted YAML upgrade due issues with CRLF on Windows
([#201](https://github.com/go-task/task/issues/201), [go-yaml/yaml#450](https://github.com/go-yaml/yaml/issues/450)).
- Allow setting global variables through the CLI
([#192](https://github.com/go-task/task/issues/192)).
## 2.5.1 - 2019-04-27
- Fixed some issues with interactive command line tools, where sometimes

View File

@@ -1,4 +1,4 @@
version: '2'
version: '3'
includes:
docs: ./docs

View File

@@ -5,10 +5,12 @@ import (
"log"
"os"
"os/signal"
"path/filepath"
"syscall"
"github.com/go-task/task/v2"
"github.com/go-task/task/v2/internal/args"
"github.com/go-task/task/v2/internal/logger"
"github.com/spf13/pflag"
)
@@ -17,7 +19,7 @@ var (
version = "master"
)
const usage = `Usage: task [-ilfwvsd] [--init] [--list] [--force] [--watch] [--verbose] [--silent] [--dir] [--dry] [--summary] [task...]
const usage = `Usage: task [-ilfwvsd] [--init] [--list] [--force] [--watch] [--verbose] [--silent] [--dir] [--taskfile] [--dry] [--summary] [task...]
Runs the specified task(s). Falls back to the "default" task if no task name
was specified, or lists all tasks if an unknown task name was specified.
@@ -26,12 +28,14 @@ Example: 'task hello' with the following 'Taskfile.yml' file will generate an
'output.txt' file with the content "hello".
'''
hello:
cmds:
- echo "I am going to write a file named 'output.txt' now."
- echo "hello" > output.txt
generates:
- output.txt
version: '3'
tasks:
hello:
cmds:
- echo "I am going to write a file named 'output.txt' now."
- echo "hello" > output.txt
generates:
- output.txt
'''
Options:
@@ -58,7 +62,9 @@ func main() {
dry bool
summary bool
dir string
entrypoint string
output string
color bool
)
pflag.BoolVar(&versionFlag, "version", false, "show Task version")
@@ -72,7 +78,9 @@ func main() {
pflag.BoolVar(&dry, "dry", false, "compiles and prints tasks in the order that they would be run, without executing them")
pflag.BoolVar(&summary, "summary", false, "show summary about a task")
pflag.StringVarP(&dir, "dir", "d", "", "sets directory of execution")
pflag.StringVarP(&entrypoint, "taskfile", "t", "", `choose which Taskfile to run. Defaults to "Taskfile.yml"`)
pflag.StringVarP(&output, "output", "o", "", "sets output style: [interleaved|group|prefixed]")
pflag.BoolVarP(&color, "color", "c", true, "colored output")
pflag.Parse()
if versionFlag {
@@ -91,14 +99,27 @@ func main() {
return
}
if dir != "" && entrypoint != "" {
log.Fatal("task: You can't set both --dir and --taskfile")
return
}
if entrypoint != "" {
dir = filepath.Dir(entrypoint)
entrypoint = filepath.Base(entrypoint)
} else {
entrypoint = "Taskfile.yml"
}
e := task.Executor{
Force: force,
Watch: watch,
Verbose: verbose,
Silent: silent,
Dir: dir,
Dry: dry,
Summary: summary,
Force: force,
Watch: watch,
Verbose: verbose,
Silent: silent,
Dir: dir,
Dry: dry,
Entrypoint: entrypoint,
Summary: summary,
Color: color,
Stdin: os.Stdin,
Stdout: os.Stdout,
@@ -117,13 +138,13 @@ func main() {
arguments := pflag.Args()
if len(arguments) == 0 {
log.Println("task: No argument given, trying default task")
e.Logger.Errf(logger.Yellow, "task: No argument given, trying default task")
arguments = []string{"default"}
}
calls, err := args.Parse(arguments...)
if err != nil {
log.Fatal(err)
calls, globals := args.Parse(arguments...)
for name, value := range globals {
e.Taskfile.Vars[name] = value
}
ctx := context.Background()
@@ -132,14 +153,15 @@ func main() {
}
if status {
if err = e.Status(ctx, calls...); err != nil {
if err := e.Status(ctx, calls...); err != nil {
log.Fatal(err)
}
return
}
if err := e.Run(ctx, calls...); err != nil {
log.Fatal(err)
e.Logger.Errf(logger.Red, "%v", err)
os.Exit(1)
}
}

View File

@@ -1,4 +1,4 @@
version: '2'
version: '3'
tasks:
install:

View File

@@ -8,13 +8,15 @@ The `task_checksums.txt` file contains the sha256 checksum for each file.
## Homebrew
If you're on macOS and have [Homebrew][homebrew] installed, getting Task is
as simple as running:
If you're on macOS or Linux and have [Homebrew][homebrew] installed, getting
Task is as simple as running:
```bash
brew install go-task/tap/go-task
```
> This installation method is only currently supported on amd64 architectures.
## Snap
Task is available for [Snapcraft][snapcraft], but keep in mind that your

View File

@@ -15,6 +15,8 @@ available, but not `3.0.0+`.
## Version 1
> NOTE: Taskfiles in version 1 are not supported on Task >= v3.0.0 anymore.
In the first version of the `Taskfile`, the `version:` key was not available,
because the tasks was in the root of the YAML document. Like this:
@@ -141,6 +143,21 @@ includes:
docker: ./DockerTasks.yml
```
## Version 2.6
Version 2.6 comes with `preconditions` stanza in tasks.
```yaml
version: '2'
tasks:
upload_environment:
preconditions:
- test -f .env
cmds:
- aws s3 cp .env s3://myenvironment
```
Please check the [documentation][includes]
[output]: usage.md#output-syntax

View File

@@ -148,6 +148,8 @@ tasks:
- caddy
```
If the directory doesn't exist, `task` creates it.
## Task dependencies
You may have tasks that depend on others. Just pointing them on `deps` will
@@ -264,6 +266,8 @@ The above syntax is also supported in `deps`.
## Prevent unnecessary work
### By fingerprinting locally generated files and their sources
If a task generates something, you can inform Task the source and generated
files, so Task will prevent to run them if not necessary.
@@ -319,6 +323,8 @@ tasks:
> TIP: method `none` skips any validation and always run the task.
### Using programmatic checks to indicate a task is up to date.
Alternatively, you can inform a sequence of tests as `status`. If no error
is returned (exit status 0), the task is considered up-to-date:
@@ -338,18 +344,86 @@ tasks:
- test -f directory/file2.txt
```
Normally, you would use `sources` in combination with
`generates` - but for tasks that generate remote artifacts (Docker images,
deploys, CD releases) the checksum source and timestamps require either
access to the artifact or for an out-of-band refresh of the `.checksum`
fingerprint file.
Two special variables `{{.CHECKSUM}}` and `{{.TIMESTAMP}}` are available
for interpolation within `status` commands, depending on the method assigned
to fingerprint the sources. Only `source` globs are fingerprinted.
Note that the `{{.TIMESTAMP}}` variable is a "live" Go `time.Time` struct, and
can be formatted using any of the methods that `time.Time` responds to.
See [the Go Time documentation](https://golang.org/pkg/time/) for more information.
You can use `--force` or `-f` if you want to force a task to run even when
up-to-date.
Also, `task --status [tasks]...` will exit with a non-zero exit code if any of
the tasks are not up-to-date.
### Using programmatic checks to cancel execution of an task and it's dependencies
In addition to `status` checks, there are also `preconditions` checks, which are
the logical inverse of `status` checks. That is, if you need a certain set of
conditions to be _true_ you can use the `preconditions` stanza.
`preconditions` are similar to `status` lines except they support `sh`
expansion and they SHOULD all return 0.
```yaml
version: '2'
tasks:
generate-files:
cmds:
- mkdir directory
- touch directory/file1.txt
- touch directory/file2.txt
# test existence of files
preconditions:
- test -f .env
- sh: "[ 1 = 0 ]"
msg: "One doesn't equal Zero, Halting"
```
Preconditions can set specific failure messages that can tell
a user what steps to take using the `msg` field.
If a task has a dependency on a sub-task with a precondition, and that
precondition is not met - the calling task will fail. Note that a task
executed with a failing precondition will not run unless `--force` is
given.
Unlike `status` which will skip a task if it is up to date, and continue
executing tasks that depend on it, a `precondition` will fail a task, along
with any other tasks that depend on it.
```yaml
version: '2'
tasks:
task_will_fail:
preconditions:
- sh: "exit 1"
task_will_also_fail:
deps:
- task_will_fail
task_will_still_fail:
cmds:
- task: task_will_fail
- echo "I will not run"
```
## Variables
When doing interpolation of variables, Task will look for the below.
They are listed below in order of importance (e.g. most important first):
- Variables declared locally in the task
- Variables declared in the task definition
- Variables given while calling a task from another.
(See [Calling another task](#calling-another-task) above)
- Variables declared in the `vars:` option in the `Taskfile`
@@ -371,6 +445,12 @@ right before.
$ task write-file FILE=file.txt "CONTENT=Hello, World!" print "MESSAGE=All done!"
```
If you want to set global variables using this syntax, give it before any task:
```bash
$ task OUTPUT=file.txt generate-file
```
Example of locally declared vars:
```yaml
@@ -379,7 +459,7 @@ version: '2'
tasks:
print-var:
cmds:
echo "{{.VAR}}"
- echo "{{.VAR}}"
vars:
VAR: Hello!
```
@@ -455,7 +535,7 @@ This works for all types of variables.
Task parse commands as [Go's template engine][gotemplate] before executing
them. Variables are accessible through dot syntax (`.VARNAME`).
All functions by the Go's [sprig lib](http://masterminds.github.io/sprig/)
All functions by the Go's [slim-sprig lib](https://go-task.github.io/slim-sprig/)
are available. The following example gets the current date in a given format:
```yaml
@@ -582,7 +662,7 @@ dependencies:
commands:
- your-release-tool
```
If a summary is missing, the description will be printed.
If a summary is missing, the description will be printed.
If the task does not have a summary or a description, a warning is printed.
Please note: *showing the summary will not execute the command*.

23
go.mod
View File

@@ -1,23 +1,16 @@
module github.com/go-task/task/v2
require (
github.com/Masterminds/semver v1.4.2
github.com/Masterminds/sprig v2.16.0+incompatible
github.com/aokoli/goutils v1.0.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/uuid v1.0.0 // indirect
github.com/huandu/xstrings v1.1.0 // indirect
github.com/imdario/mergo v0.3.6 // indirect
github.com/kr/pretty v0.1.0 // indirect
github.com/fatih/color v1.7.0
github.com/go-task/slim-sprig v0.0.0-20190623010546-24867827a98b
github.com/mattn/go-colorable v0.1.2 // indirect
github.com/mattn/go-zglob v0.0.1
github.com/mitchellh/go-homedir v1.0.0
github.com/radovskyb/watcher v1.0.5
github.com/spf13/pflag v1.0.3
github.com/stretchr/testify v1.3.0
golang.org/x/crypto v0.0.0-20180830192347-182538f80094 // indirect
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d // indirect
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f
golang.org/x/sys v0.0.0-20180831094639-fa5fdf94c789 // indirect
gopkg.in/yaml.v3 v3.0.0-20190409140830-cdc409dda467
mvdan.cc/sh v2.6.4+incompatible
golang.org/x/sync v0.0.0-20190423024810-112230192c58
gopkg.in/yaml.v2 v2.2.1
mvdan.cc/sh/v3 v3.0.0-alpha2.0.20190908210725-4a0ebd2f3c1b
)
go 1.13

55
go.sum
View File

@@ -1,47 +1,50 @@
github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc=
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/sprig v2.16.0+incompatible h1:QZbMUPxRQ50EKAq3LFMnxddMu88/EUUG3qmxwtDmPsY=
github.com/Masterminds/sprig v2.16.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
github.com/aokoli/goutils v1.0.1 h1:7fpzNGoJ3VA8qcrm++XEE1QUe0mIwNeLa02Nwq7RDkg=
github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ=
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/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/huandu/xstrings v1.1.0 h1:9oZY6Z/H3A1gytJxzuicbmV5QoR8M1TAPcn9WTg7vqg=
github.com/huandu/xstrings v1.1.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/go-task/slim-sprig v0.0.0-20190623010546-24867827a98b h1:z6iCP1USASmEZtKTzynd/rP4vOtBLlsD3v24wItbJIs=
github.com/go-task/slim-sprig v0.0.0-20190623010546-24867827a98b/go.mod h1:XLIiFDBy2M8pA/fEL5rx9xr2EAzrDEO0S5brm5iekOE=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-zglob v0.0.1 h1:xsEx/XUoVlI6yXjqBK062zYhRTZltCNmYPx6v+8DNaY=
github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=
github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
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/radovskyb/watcher v1.0.5 h1:wqt7gb+HjGacvFoLTKeT44C+XVPxu7bvHvKT1IvZ7rw=
github.com/radovskyb/watcher v1.0.5/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v0.0.0-20180319223459-c679ae2cc0cb/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
golang.org/x/crypto v0.0.0-20180830192347-182538f80094 h1:rVTAlhYa4+lCfNxmAIEOGQRoD23UqP72M3+rSWVGDTg=
golang.org/x/crypto v0.0.0-20180830192347-182538f80094/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d h1:g9qWBGx4puODJTMVyoPrpoxPFgVGd+z1DZwjfRu4d0I=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180831094639-fa5fdf94c789 h1:T8D7l6WB3tLu+VpKvw06ieD/OhBi1XpJmG1U/FtttZg=
golang.org/x/sys v0.0.0-20180831094639-fa5fdf94c789/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 h1:LepdCS8Gf/MVejFIt8lsiexZATdoGVyp5bcyS+rYoUI=
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
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.v3 v3.0.0-20190409140830-cdc409dda467 h1:w3VhdSYz2sIVz54Ta/eDCCfCQ4fQkDgRxMACggArIUw=
gopkg.in/yaml.v3 v3.0.0-20190409140830-cdc409dda467/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
mvdan.cc/sh v2.6.4+incompatible h1:eD6tDeh0pw+/TOTI1BBEryZ02rD2nMcFsgcvde7jffM=
mvdan.cc/sh v2.6.4+incompatible/go.mod h1:IeeQbZq+x2SUGBensq/jge5lLQbS3XT2ktyp3wrt4x8=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
mvdan.cc/sh/v3 v3.0.0-alpha2.0.20190908210725-4a0ebd2f3c1b h1:kzTXBacNrjp7n8ncNC894X9NUeL8yeKVS5MS0bigJwE=
mvdan.cc/sh/v3 v3.0.0-alpha2.0.20190908210725-4a0ebd2f3c1b/go.mod h1:6Cd5lQRZZIHeKAdYoLvhhse0oDOIrS5gBoGacWnuiUE=

View File

@@ -5,6 +5,7 @@ import (
"sort"
"text/tabwriter"
"github.com/go-task/task/v2/internal/logger"
"github.com/go-task/task/v2/internal/taskfile"
)
@@ -12,10 +13,10 @@ import (
func (e *Executor) PrintTasksHelp() {
tasks := e.tasksWithDesc()
if len(tasks) == 0 {
e.Logger.Outf("task: No tasks with description available")
e.Logger.Outf(logger.Yellow, "task: No tasks with description available")
return
}
e.Logger.Outf("task: Available tasks for this project:")
e.Logger.Outf(logger.Default, "task: Available tasks for this project:")
// Format in tab-separated columns with a tab stop of 8.
w := tabwriter.NewWriter(e.Stdout, 0, 8, 0, '\t', 0)

View File

@@ -10,7 +10,7 @@ import (
const defaultTaskfile = `# https://taskfile.dev
version: '2'
version: '3'
vars:
GREETING: Hello, World!

View File

@@ -1,36 +1,43 @@
package args
import (
"errors"
"strings"
"github.com/go-task/task/v2/internal/taskfile"
)
var (
// ErrVariableWithoutTask is returned when variables are given before any task
ErrVariableWithoutTask = errors.New("task: variable given before any task")
)
// Parse parses command line argument: tasks and vars of each task
func Parse(args ...string) ([]taskfile.Call, error) {
func Parse(args ...string) ([]taskfile.Call, taskfile.Vars) {
var calls []taskfile.Call
var globals taskfile.Vars
for _, arg := range args {
if !strings.Contains(arg, "=") {
calls = append(calls, taskfile.Call{Task: arg})
continue
}
if len(calls) < 1 {
return nil, ErrVariableWithoutTask
}
if globals == nil {
globals = taskfile.Vars{}
}
if calls[len(calls)-1].Vars == nil {
calls[len(calls)-1].Vars = make(taskfile.Vars)
}
name, value := splitVar(arg)
globals[name] = taskfile.Var{Static: value}
} else {
if calls[len(calls)-1].Vars == nil {
calls[len(calls)-1].Vars = make(taskfile.Vars)
}
pair := strings.SplitN(arg, "=", 2)
calls[len(calls)-1].Vars[pair[0]] = taskfile.Var{Static: pair[1]}
name, value := splitVar((arg))
calls[len(calls)-1].Vars[name] = taskfile.Var{Static: value}
}
}
return calls, nil
return calls, globals
}
func splitVar(s string) (string, string) {
pair := strings.SplitN(s, "=", 2)
return pair[0], pair[1]
}

View File

@@ -12,13 +12,13 @@ import (
func TestArgs(t *testing.T) {
tests := []struct {
Args []string
Expected []taskfile.Call
Err error
Args []string
ExpectedCalls []taskfile.Call
ExpectedGlobals taskfile.Vars
}{
{
Args: []string{"task-a", "task-b", "task-c"},
Expected: []taskfile.Call{
ExpectedCalls: []taskfile.Call{
{Task: "task-a"},
{Task: "task-b"},
{Task: "task-c"},
@@ -26,7 +26,7 @@ func TestArgs(t *testing.T) {
},
{
Args: []string{"task-a", "FOO=bar", "task-b", "task-c", "BAR=baz", "BAZ=foo"},
Expected: []taskfile.Call{
ExpectedCalls: []taskfile.Call{
{
Task: "task-a",
Vars: taskfile.Vars{
@@ -45,7 +45,7 @@ func TestArgs(t *testing.T) {
},
{
Args: []string{"task-a", "CONTENT=with some spaces"},
Expected: []taskfile.Call{
ExpectedCalls: []taskfile.Call{
{
Task: "task-a",
Vars: taskfile.Vars{
@@ -55,16 +55,22 @@ func TestArgs(t *testing.T) {
},
},
{
Args: []string{"FOO=bar", "task-a"},
Err: args.ErrVariableWithoutTask,
Args: []string{"FOO=bar", "task-a", "task-b"},
ExpectedCalls: []taskfile.Call{
{Task: "task-a"},
{Task: "task-b"},
},
ExpectedGlobals: taskfile.Vars{
"FOO": {Static: "bar"},
},
},
}
for i, test := range tests {
t.Run(fmt.Sprintf("TestArgs%d", i+1), func(t *testing.T) {
calls, err := args.Parse(test.Args...)
assert.Equal(t, test.Err, err)
assert.Equal(t, test.Expected, calls)
calls, globals := args.Parse(test.Args...)
assert.Equal(t, test.ExpectedCalls, calls)
assert.Equal(t, test.ExpectedGlobals, globals)
})
}
}

View File

@@ -1,137 +0,0 @@
package v1
import (
"bytes"
"context"
"fmt"
"strings"
"sync"
"github.com/go-task/task/v2/internal/compiler"
"github.com/go-task/task/v2/internal/execext"
"github.com/go-task/task/v2/internal/logger"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/go-task/task/v2/internal/templater"
)
var _ compiler.Compiler = &CompilerV1{}
type CompilerV1 struct {
Dir string
Vars taskfile.Vars
Logger *logger.Logger
dynamicCache map[string]string
muDynamicCache sync.Mutex
}
// GetVariables returns fully resolved variables following the priority order:
// 1. Call variables (should already have been resolved)
// 2. Environment (should not need to be resolved)
// 3. Task variables, resolved with access to:
// - call, taskvars and environment variables
// 4. Taskvars variables, resolved with access to:
// - environment variables
func (c *CompilerV1) GetVariables(t *taskfile.Task, call taskfile.Call) (taskfile.Vars, error) {
merge := func(dest taskfile.Vars, srcs ...taskfile.Vars) {
for _, src := range srcs {
for k, v := range src {
dest[k] = v
}
}
}
varsKeys := func(srcs ...taskfile.Vars) []string {
m := make(map[string]struct{})
for _, src := range srcs {
for k := range src {
m[k] = struct{}{}
}
}
lst := make([]string, 0, len(m))
for k := range m {
lst = append(lst, k)
}
return lst
}
replaceVars := func(dest taskfile.Vars, keys []string) error {
r := templater.Templater{Vars: dest}
for _, k := range keys {
v := dest[k]
dest[k] = taskfile.Var{
Static: r.Replace(v.Static),
Sh: r.Replace(v.Sh),
}
}
return r.Err()
}
resolveShell := func(dest taskfile.Vars, keys []string) error {
for _, k := range keys {
v := dest[k]
static, err := c.HandleDynamicVar(v)
if err != nil {
return err
}
dest[k] = taskfile.Var{Static: static}
}
return nil
}
update := func(dest taskfile.Vars, srcs ...taskfile.Vars) error {
merge(dest, srcs...)
// updatedKeys ensures template evaluation is run only once.
updatedKeys := varsKeys(srcs...)
if err := replaceVars(dest, updatedKeys); err != nil {
return err
}
return resolveShell(dest, updatedKeys)
}
// Resolve taskvars variables to "result" with environment override variables.
override := compiler.GetEnviron()
result := make(taskfile.Vars, len(c.Vars)+len(t.Vars)+len(override))
if err := update(result, c.Vars, override); err != nil {
return nil, err
}
// Resolve task variables to "result" with environment and call override variables.
merge(override, call.Vars)
if err := update(result, t.Vars, override); err != nil {
return nil, err
}
return result, nil
}
func (c *CompilerV1) HandleDynamicVar(v taskfile.Var) (string, error) {
if v.Static != "" || v.Sh == "" {
return v.Static, nil
}
c.muDynamicCache.Lock()
defer c.muDynamicCache.Unlock()
if c.dynamicCache == nil {
c.dynamicCache = make(map[string]string, 30)
}
if result, ok := c.dynamicCache[v.Sh]; ok {
return result, nil
}
var stdout bytes.Buffer
opts := &execext.RunCommandOptions{
Command: v.Sh,
Dir: c.Dir,
Stdout: &stdout,
Stderr: c.Logger.Stderr,
}
if err := execext.RunCommand(context.Background(), opts); err != nil {
return "", fmt.Errorf(`task: Command "%s" in taskvars file failed: %s`, opts.Command, err)
}
// Trim a single trailing newline from the result to make most command
// output easier to use in shell commands.
result := strings.TrimSuffix(stdout.String(), "\n")
c.dynamicCache[v.Sh] = result
c.Logger.VerboseErrf(`task: dynamic variable: '%s' result: '%s'`, v.Sh, result)
return result, nil
}

View File

@@ -103,7 +103,7 @@ func (c *CompilerV2) HandleDynamicVar(v taskfile.Var) (string, error) {
result := strings.TrimSuffix(stdout.String(), "\n")
c.dynamicCache[v.Sh] = result
c.Logger.VerboseErrf(`task: dynamic variable: '%s' result: '%s'`, v.Sh, result)
c.Logger.VerboseErrf(logger.Magenta, `task: dynamic variable: '%s' result: '%s'`, v.Sh, result)
return result, nil
}

View File

@@ -8,10 +8,10 @@ import (
"path/filepath"
"strings"
"mvdan.cc/sh/expand"
"mvdan.cc/sh/interp"
"mvdan.cc/sh/shell"
"mvdan.cc/sh/syntax"
"mvdan.cc/sh/v3/expand"
"mvdan.cc/sh/v3/interp"
"mvdan.cc/sh/v3/shell"
"mvdan.cc/sh/v3/syntax"
)
// RunCommandOptions is the options for the RunCommand func
@@ -49,8 +49,7 @@ func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
interp.Dir(opts.Dir),
interp.Env(expand.ListEnviron(environ...)),
interp.Module(interp.DefaultExec),
interp.Module(interp.OpenDevImpls(interp.DefaultOpen)),
interp.WithOpenModules(interp.OpenDevImpls),
interp.StdIO(opts.Stdin, opts.Stdout, opts.Stderr),
)

View File

@@ -1,38 +1,65 @@
package logger
import (
"fmt"
"io"
"github.com/fatih/color"
)
type PrintFunc func(io.Writer, string, ...interface{})
var (
Default PrintFunc = color.New(color.Reset).FprintfFunc()
Bold PrintFunc = color.New(color.Bold).FprintfFunc()
Blue PrintFunc = color.New(color.FgBlue, color.Bold).FprintfFunc()
Green PrintFunc = color.New(color.FgGreen, color.Bold).FprintfFunc()
Cyan PrintFunc = color.New(color.FgCyan, color.Bold).FprintfFunc()
Yellow PrintFunc = color.New(color.FgYellow, color.Bold).FprintfFunc()
Magenta PrintFunc = color.New(color.FgMagenta, color.Bold).FprintfFunc()
Red PrintFunc = color.New(color.FgRed, color.Bold).FprintfFunc()
)
// Logger is just a wrapper that prints stuff to STDOUT or STDERR,
// with optional color.
type Logger struct {
Stdout io.Writer
Stderr io.Writer
Verbose bool
Color bool
}
func (l *Logger) Outf(s string, args ...interface{}) {
// Outf prints stuff to STDOUT.
func (l *Logger) Outf(print PrintFunc, s string, args ...interface{}) {
if len(args) == 0 {
s, args = "%s", []interface{}{s}
}
fmt.Fprintf(l.Stdout, s+"\n", args...)
if !l.Color {
print = Default
}
print(l.Stdout, s+"\n", args...)
}
func (l *Logger) VerboseOutf(s string, args ...interface{}) {
// VerboseOutf prints stuff to STDOUT if verbose mode is enabled.
func (l *Logger) VerboseOutf(print PrintFunc, s string, args ...interface{}) {
if l.Verbose {
l.Outf(s, args...)
l.Outf(print, s, args...)
}
}
func (l *Logger) Errf(s string, args ...interface{}) {
// Errf prints stuff to STDERR.
func (l *Logger) Errf(print PrintFunc, s string, args ...interface{}) {
if len(args) == 0 {
s, args = "%s", []interface{}{s}
}
fmt.Fprintf(l.Stderr, s+"\n", args...)
if !l.Color {
print = Default
}
print(l.Stderr, s+"\n", args...)
}
func (l *Logger) VerboseErrf(s string, args ...interface{}) {
// VerboseErrf prints stuff to STDERR if verbose mode is enabled.
func (l *Logger) VerboseErrf(print PrintFunc, s string, args ...interface{}) {
if l.Verbose {
l.Errf(s, args...)
l.Errf(print, s, args...)
}
}

View File

@@ -14,20 +14,25 @@ import (
// Checksum validades if a task is up to date by calculating its source
// files checksum
type Checksum struct {
Dir string
Task string
Sources []string
Dry bool
Dir string
Task string
Sources []string
Generates []string
Dry bool
}
// IsUpToDate implements the Checker interface
func (c *Checksum) IsUpToDate() (bool, error) {
if len(c.Sources) == 0 {
return false, nil
}
checksumFile := c.checksumFilePath()
data, _ := ioutil.ReadFile(checksumFile)
oldMd5 := strings.TrimSpace(string(data))
sources, err := glob(c.Dir, c.Sources)
sources, err := globs(c.Dir, c.Sources)
if err != nil {
return false, err
}
@@ -43,6 +48,23 @@ func (c *Checksum) IsUpToDate() (bool, error) {
return false, err
}
}
if len(c.Generates) > 0 {
// For each specified 'generates' field, check whether the files actually exist
for _, g := range c.Generates {
generates, err := glob(c.Dir, g)
if os.IsNotExist(err) {
return false, nil
}
if err != nil {
return false, err
}
if len(generates) == 0 {
return false, nil
}
}
}
return oldMd5 == newMd5, nil
}
@@ -50,21 +72,14 @@ func (c *Checksum) checksum(files ...string) (string, error) {
h := md5.New()
for _, f := range files {
// also sum the filename, so checksum changes for renaming a file
if _, err := io.Copy(h, strings.NewReader(filepath.Base(f))); err != nil {
return "", err
}
f, err := os.Open(f)
if err != nil {
return "", err
}
info, err := f.Stat()
if err != nil {
return "", err
}
if info.IsDir() {
continue
}
// also sum the filename, so checksum changes for renaming a file
if _, err = io.Copy(h, strings.NewReader(info.Name())); err != nil {
return "", err
}
if _, err = io.Copy(h, f); err != nil {
return "", err
}
@@ -73,11 +88,21 @@ func (c *Checksum) checksum(files ...string) (string, error) {
return fmt.Sprintf("%x", h.Sum(nil)), nil
}
// Value implements the Checker Interface
func (c *Checksum) Value() (interface{}, error) {
return c.checksum()
}
// OnError implements the Checker interface
func (c *Checksum) OnError() error {
return os.Remove(c.checksumFilePath())
}
// Kind implements the Checker Interface
func (*Checksum) Kind() string {
return "checksum"
}
func (c *Checksum) checksumFilePath() string {
return filepath.Join(c.Dir, ".task", "checksum", c.normalizeFilename(c.Task))
}

View File

@@ -1,6 +1,7 @@
package status
import (
"os"
"path/filepath"
"sort"
@@ -9,21 +10,41 @@ import (
"github.com/mattn/go-zglob"
)
func glob(dir string, globs []string) (files []string, err error) {
func globs(dir string, globs []string) ([]string, error) {
files := make([]string, 0)
for _, g := range globs {
if !filepath.IsAbs(g) {
g = filepath.Join(dir, g)
}
g, err = execext.Expand(g)
if err != nil {
return nil, err
}
f, err := zglob.Glob(g)
f, err := glob(dir, g)
if err != nil {
continue
}
files = append(files, f...)
}
sort.Strings(files)
return
return files, nil
}
func glob(dir string, g string) ([]string, error) {
files := make([]string, 0)
if !filepath.IsAbs(g) {
g = filepath.Join(dir, g)
}
g, err := execext.Expand(g)
if err != nil {
return nil, err
}
fs, err := zglob.Glob(g)
if err != nil {
return nil, err
}
for _, f := range fs {
info, err := os.Stat(f)
if err != nil {
return nil, err
}
if info.IsDir() {
continue
}
files = append(files, f)
}
return files, nil
}

View File

@@ -8,6 +8,15 @@ func (None) IsUpToDate() (bool, error) {
return false, nil
}
// Value implements the Checker interface
func (None) Value() (interface{}, error) {
return "", nil
}
func (None) Kind() string {
return "none"
}
// OnError implements the Checker interface
func (None) OnError() error {
return nil

View File

@@ -9,5 +9,7 @@ var (
// Checker is an interface that checks if the status is up-to-date
type Checker interface {
IsUpToDate() (bool, error)
Value() (interface{}, error)
OnError() error
Kind() string
}

View File

@@ -19,11 +19,11 @@ func (t *Timestamp) IsUpToDate() (bool, error) {
return false, nil
}
sources, err := glob(t.Dir, t.Sources)
sources, err := globs(t.Dir, t.Sources)
if err != nil {
return false, nil
}
generates, err := glob(t.Dir, t.Generates)
generates, err := globs(t.Dir, t.Generates)
if err != nil {
return false, nil
}
@@ -41,6 +41,29 @@ func (t *Timestamp) IsUpToDate() (bool, error) {
return !generatesMinTime.Before(sourcesMaxTime), nil
}
func (t *Timestamp) Kind() string {
return "timestamp"
}
// Value implements the Checker Interface
func (t *Timestamp) Value() (interface{}, error) {
sources, err := globs(t.Dir, t.Sources)
if err != nil {
return time.Now(), err
}
sourcesMaxTime, err := getMaxTime(sources...)
if err != nil {
return time.Now(), err
}
if sourcesMaxTime.IsZero() {
return time.Unix(0, 0), nil
}
return sourcesMaxTime, nil
}
func getMinTime(files ...string) (time.Time, error) {
var t time.Time
for _, f := range files {

View File

@@ -20,8 +20,8 @@ func printSpaceBetweenSummaries(l *logger.Logger, i int) {
return
}
l.Outf("")
l.Outf("")
l.Outf(logger.Default, "")
l.Outf(logger.Default, "")
}
func PrintTask(l *logger.Logger, t *taskfile.Task) {
@@ -50,14 +50,14 @@ func printTaskSummary(l *logger.Logger, t *taskfile.Task) {
for i, line := range lines {
notLastLine := i+1 < len(lines)
if notLastLine || line != "" {
l.Outf(line)
l.Outf(logger.Default, line)
}
}
}
func printTaskName(l *logger.Logger, t *taskfile.Task) {
l.Outf("task: %s", t.Task)
l.Outf("")
l.Outf(logger.Default, "task: %s", t.Task)
l.Outf(logger.Default, "")
}
func hasDescription(t *taskfile.Task) bool {
@@ -65,11 +65,11 @@ func hasDescription(t *taskfile.Task) bool {
}
func printTaskDescription(l *logger.Logger, t *taskfile.Task) {
l.Outf(t.Desc)
l.Outf(logger.Default, t.Desc)
}
func printNoDescriptionOrSummary(l *logger.Logger) {
l.Outf("(task does not have description or summary)")
l.Outf(logger.Default, "(task does not have description or summary)")
}
func printTaskDependencies(l *logger.Logger, t *taskfile.Task) {
@@ -77,11 +77,11 @@ func printTaskDependencies(l *logger.Logger, t *taskfile.Task) {
return
}
l.Outf("")
l.Outf("dependencies:")
l.Outf(logger.Default, "")
l.Outf(logger.Default, "dependencies:")
for _, d := range t.Deps {
l.Outf(" - %s", d.Task)
l.Outf(logger.Default, " - %s", d.Task)
}
}
@@ -90,14 +90,14 @@ func printTaskCommands(l *logger.Logger, t *taskfile.Task) {
return
}
l.Outf("")
l.Outf("commands:")
l.Outf(logger.Default, "")
l.Outf(logger.Default, "commands:")
for _, c := range t.Cmds {
isCommand := c.Cmd != ""
if isCommand {
l.Outf(" - %s", c.Cmd)
l.Outf(logger.Default, " - %s", c.Cmd)
} else {
l.Outf(" - Task: %s", c.Task)
l.Outf(logger.Default, " - Task: %s", c.Task)
}
}
}

View File

@@ -0,0 +1,45 @@
package taskfile
import (
"errors"
"fmt"
)
var (
// ErrCantUnmarshalPrecondition is returned for invalid precond YAML.
ErrCantUnmarshalPrecondition = errors.New("task: Can't unmarshal precondition value")
)
// Precondition represents a precondition necessary for a task to run
type Precondition struct {
Sh string
Msg string
}
// UnmarshalYAML implements yaml.Unmarshaler interface.
func (p *Precondition) UnmarshalYAML(unmarshal func(interface{}) error) error {
var cmd string
if err := unmarshal(&cmd); err == nil {
p.Sh = cmd
p.Msg = fmt.Sprintf("`%s` failed", cmd)
return nil
}
var sh struct {
Sh string
Msg string
}
if err := unmarshal(&sh); err != nil {
return err
}
p.Sh = sh.Sh
p.Msg = sh.Msg
if p.Msg == "" {
p.Msg = fmt.Sprintf("%s failed", sh.Sh)
}
return nil
}

View File

@@ -0,0 +1,48 @@
package taskfile_test
import (
"testing"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v2"
)
func TestPreconditionParse(t *testing.T) {
tests := []struct {
content string
v interface{}
expected interface{}
}{
{
"test -f foo.txt",
&taskfile.Precondition{},
&taskfile.Precondition{Sh: `test -f foo.txt`, Msg: "`test -f foo.txt` failed"},
},
{
"sh: '[ 1 = 0 ]'",
&taskfile.Precondition{},
&taskfile.Precondition{Sh: "[ 1 = 0 ]", Msg: "[ 1 = 0 ] failed"},
},
{`
sh: "[ 1 = 2 ]"
msg: "1 is not 2"
`,
&taskfile.Precondition{},
&taskfile.Precondition{Sh: "[ 1 = 2 ]", Msg: "1 is not 2"},
},
{`
sh: "[ 1 = 2 ]"
msg: "1 is not 2"
`,
&taskfile.Precondition{},
&taskfile.Precondition{Sh: "[ 1 = 2 ]", Msg: "1 is not 2"},
},
}
for _, test := range tests {
err := yaml.Unmarshal([]byte(test.content), test.v)
assert.NoError(t, err)
assert.Equal(t, test.expected, test.v)
}
}

View File

@@ -9,22 +9,19 @@ import (
"github.com/go-task/task/v2/internal/taskfile"
"gopkg.in/yaml.v3"
"gopkg.in/yaml.v2"
)
var (
// ErrIncludedTaskfilesCantHaveIncludes is returned when a included Taskfile contains includes
ErrIncludedTaskfilesCantHaveIncludes = errors.New("task: Included Taskfiles can't have includes. Please, move the include to the main Taskfile")
// ErrNoTaskfileFound is returned when Taskfile.yml is not found
ErrNoTaskfileFound = errors.New(`task: No Taskfile.yml found. Use "task --init" to create a new one`)
)
// Taskfile reads a Taskfile for a given directory
func Taskfile(dir string) (*taskfile.Taskfile, error) {
path := filepath.Join(dir, "Taskfile.yml")
func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
path := filepath.Join(dir, entrypoint)
if _, err := os.Stat(path); err != nil {
return nil, ErrNoTaskfileFound
return nil, fmt.Errorf(`task: No Taskfile found on "%s". Use "task --init" to create a new one`, path)
}
t, err := readTaskfile(path)
if err != nil {

View File

@@ -8,7 +8,7 @@ import (
"github.com/go-task/task/v2/internal/taskfile"
"gopkg.in/yaml.v3"
"gopkg.in/yaml.v2"
)
// Taskvars reads a Taskvars for a given directory

View File

@@ -5,19 +5,20 @@ type Tasks map[string]*Task
// Task represents a task
type Task struct {
Task string
Cmds []*Cmd
Deps []*Dep
Desc string
Summary string
Sources []string
Generates []string
Status []string
Dir string
Vars Vars
Env Vars
Silent bool
Method string
Prefix string
IgnoreError bool `yaml:"ignore_error"`
Task string
Cmds []*Cmd
Deps []*Dep
Desc string
Summary string
Sources []string
Generates []string
Status []string
Preconditions []*Precondition
Dir string
Vars Vars
Env Vars
Silent bool
Method string
Prefix string
IgnoreError bool `yaml:"ignore_error"`
}

View File

@@ -5,6 +5,7 @@ type Taskfile struct {
Version string
Expansions int
Output string
Method string
Includes map[string]string
Vars Vars
Env Vars
@@ -13,15 +14,11 @@ type Taskfile struct {
// UnmarshalYAML implements yaml.Unmarshaler interface
func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
if err := unmarshal(&tf.Tasks); err == nil {
tf.Version = "1"
return nil
}
var taskfile struct {
Version string
Expansions int
Output string
Method string
Includes map[string]string
Vars Vars
Env Vars
@@ -33,6 +30,7 @@ func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
tf.Version = taskfile.Version
tf.Expansions = taskfile.Expansions
tf.Output = taskfile.Output
tf.Method = taskfile.Method
tf.Includes = taskfile.Includes
tf.Vars = taskfile.Vars
tf.Env = taskfile.Env
@@ -40,5 +38,8 @@ func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
if tf.Expansions <= 0 {
tf.Expansions = 2
}
if tf.Vars == nil {
tf.Vars = make(Vars)
}
return nil
}

View File

@@ -6,7 +6,7 @@ import (
"github.com/go-task/task/v2/internal/taskfile"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
"gopkg.in/yaml.v2"
)
func TestCmdParse(t *testing.T) {

View File

@@ -13,17 +13,22 @@ var (
// Vars is a string[string] variables map.
type Vars map[string]Var
// ToStringMap converts Vars to a string map containing only the static
// ToCacheMap converts Vars to a map containing only the static
// variables
func (vs Vars) ToStringMap() (m map[string]string) {
m = make(map[string]string, len(vs))
func (vs Vars) ToCacheMap() (m map[string]interface{}) {
m = make(map[string]interface{}, len(vs))
for k, v := range vs {
if v.Sh != "" {
// Dynamic variable is not yet resolved; trigger
// <no value> to be used in templates.
continue
}
m[k] = v.Static
if v.Live != nil {
m[k] = v.Live
} else {
m[k] = v.Static
}
}
return
}
@@ -31,6 +36,7 @@ func (vs Vars) ToStringMap() (m map[string]string) {
// Var represents either a static or dynamic variable.
type Var struct {
Static string
Live interface{}
Sh string
}

View File

@@ -1,46 +0,0 @@
package version
import (
"github.com/Masterminds/semver"
)
var (
v1 = mustVersion("1")
v2 = mustVersion("2")
v21 = mustVersion("2.1")
v22 = mustVersion("2.2")
v23 = mustVersion("2.3")
)
// IsV1 returns if is a given Taskfile version is version 1
func IsV1(v *semver.Constraints) bool {
return v.Check(v1)
}
// IsV2 returns if is a given Taskfile version is at least version 2
func IsV2(v *semver.Constraints) bool {
return v.Check(v2)
}
// IsV21 returns if is a given Taskfile version is at least version 2.1
func IsV21(v *semver.Constraints) bool {
return v.Check(v21)
}
// IsV22 returns if is a given Taskfile version is at least version 2.2
func IsV22(v *semver.Constraints) bool {
return v.Check(v22)
}
// IsV23 returns if is a given Taskfile version is at least version 2.3
func IsV23(v *semver.Constraints) bool {
return v.Check(v23)
}
func mustVersion(s string) *semver.Version {
v, err := semver.NewVersion(s)
if err != nil {
panic(err)
}
return v
}

View File

@@ -6,7 +6,7 @@ import (
"strings"
"text/template"
"github.com/Masterminds/sprig"
"github.com/go-task/slim-sprig"
)
var (

View File

@@ -14,8 +14,12 @@ import (
type Templater struct {
Vars taskfile.Vars
strMap map[string]string
err error
cacheMap map[string]interface{}
err error
}
func (r *Templater) ResetCache() {
r.cacheMap = r.Vars.ToCacheMap()
}
func (r *Templater) Replace(str string) string {
@@ -29,12 +33,12 @@ func (r *Templater) Replace(str string) string {
return ""
}
if r.strMap == nil {
r.strMap = r.Vars.ToStringMap()
if r.cacheMap == nil {
r.cacheMap = r.Vars.ToCacheMap()
}
var b bytes.Buffer
if err = templ.Execute(&b, r.strMap); err != nil {
if err = templ.Execute(&b, r.cacheMap); err != nil {
r.err = err
return ""
}
@@ -62,6 +66,7 @@ func (r *Templater) ReplaceVars(vars taskfile.Vars) taskfile.Vars {
for k, v := range vars {
new[k] = taskfile.Var{
Static: r.Replace(v.Static),
Live: v.Live,
Sh: r.Replace(v.Sh),
}
}

32
precondition.go Normal file
View File

@@ -0,0 +1,32 @@
package task
import (
"context"
"errors"
"github.com/go-task/task/v2/internal/execext"
"github.com/go-task/task/v2/internal/logger"
"github.com/go-task/task/v2/internal/taskfile"
)
var (
// ErrPreconditionFailed is returned when a precondition fails
ErrPreconditionFailed = errors.New("task: precondition not met")
)
func (e *Executor) areTaskPreconditionsMet(ctx context.Context, t *taskfile.Task) (bool, error) {
for _, p := range t.Preconditions {
err := execext.RunCommand(ctx, &execext.RunCommandOptions{
Command: p.Sh,
Dir: t.Dir,
Env: getEnviron(t),
})
if err != nil {
e.Logger.Errf(logger.Magenta, "task: %s", p.Msg)
return false, ErrPreconditionFailed
}
}
return true, nil
}

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"github.com/go-task/task/v2/internal/execext"
"github.com/go-task/task/v2/internal/logger"
"github.com/go-task/task/v2/internal/status"
"github.com/go-task/task/v2/internal/taskfile"
)
@@ -49,24 +50,37 @@ func (e *Executor) statusOnError(t *taskfile.Task) error {
}
func (e *Executor) getStatusChecker(t *taskfile.Task) (status.Checker, error) {
switch t.Method {
case "", "timestamp":
return &status.Timestamp{
Dir: t.Dir,
Sources: t.Sources,
Generates: t.Generates,
}, nil
method := t.Method
if method == "" {
method = e.Taskfile.Method
}
switch method {
case "timestamp":
return e.timestampChecker(t), nil
case "checksum":
return &status.Checksum{
Dir: t.Dir,
Task: t.Task,
Sources: t.Sources,
Dry: e.Dry,
}, nil
return e.checksumChecker(t), nil
case "none":
return status.None{}, nil
default:
return nil, fmt.Errorf(`task: invalid method "%s"`, t.Method)
return nil, fmt.Errorf(`task: invalid method "%s"`, method)
}
}
func (e *Executor) timestampChecker(t *taskfile.Task) status.Checker {
return &status.Timestamp{
Dir: t.Dir,
Sources: t.Sources,
Generates: t.Generates,
}
}
func (e *Executor) checksumChecker(t *taskfile.Task) status.Checker {
return &status.Checksum{
Dir: t.Dir,
Task: t.Task,
Sources: t.Sources,
Generates: t.Generates,
Dry: e.Dry,
}
}
@@ -78,8 +92,10 @@ func (e *Executor) isTaskUpToDateStatus(ctx context.Context, t *taskfile.Task) (
Env: getEnviron(t),
})
if err != nil {
e.Logger.VerboseOutf(logger.Yellow, "task: status command %s exited non-zero: %s", s, err)
return false, nil
}
e.Logger.VerboseOutf(logger.Yellow, "task: status command %s exited zero", s)
}
return true, nil
}

169
task.go
View File

@@ -2,13 +2,15 @@ package task
import (
"context"
"errors"
"fmt"
"io"
"os"
"strconv"
"sync"
"sync/atomic"
"github.com/go-task/task/v2/internal/compiler"
compilerv1 "github.com/go-task/task/v2/internal/compiler/v1"
compilerv2 "github.com/go-task/task/v2/internal/compiler/v2"
"github.com/go-task/task/v2/internal/execext"
"github.com/go-task/task/v2/internal/logger"
@@ -16,9 +18,7 @@ import (
"github.com/go-task/task/v2/internal/summary"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/go-task/task/v2/internal/taskfile/read"
"github.com/go-task/task/v2/internal/taskfile/version"
"github.com/Masterminds/semver"
"golang.org/x/sync/errgroup"
)
@@ -31,13 +31,16 @@ const (
// Executor executes a Taskfile
type Executor struct {
Taskfile *taskfile.Taskfile
Dir string
Force bool
Watch bool
Verbose bool
Silent bool
Dry bool
Summary bool
Dir string
Entrypoint string
Force bool
Watch bool
Verbose bool
Silent bool
Dry bool
Summary bool
Color bool
Stdin io.Reader
Stdout io.Writer
@@ -51,6 +54,7 @@ type Executor struct {
taskvars taskfile.Vars
taskCallCount map[string]*int32
mkdirMutexMap map[string]*sync.Mutex
}
// Run runs Task
@@ -83,8 +87,12 @@ func (e *Executor) Run(ctx context.Context, calls ...taskfile.Call) error {
// Setup setups Executor's internal state
func (e *Executor) Setup() error {
if e.Entrypoint == "" {
e.Entrypoint = "Taskfile.yml"
}
var err error
e.Taskfile, err = read.Taskfile(e.Dir)
e.Taskfile, err = read.Taskfile(e.Dir, e.Entrypoint)
if err != nil {
return err
}
@@ -93,11 +101,6 @@ func (e *Executor) Setup() error {
return err
}
v, err := semver.NewConstraint(e.Taskfile.Version)
if err != nil {
return fmt.Errorf(`task: could not parse taskfile version "%s": %v`, e.Taskfile.Version, err)
}
if e.Stdin == nil {
e.Stdin = os.Stdin
}
@@ -111,32 +114,47 @@ func (e *Executor) Setup() error {
Stdout: e.Stdout,
Stderr: e.Stderr,
Verbose: e.Verbose,
}
switch {
case version.IsV1(v):
e.Compiler = &compilerv1.CompilerV1{
Dir: e.Dir,
Vars: e.taskvars,
Logger: e.Logger,
}
case version.IsV2(v), version.IsV21(v), version.IsV22(v):
e.Compiler = &compilerv2.CompilerV2{
Dir: e.Dir,
Taskvars: e.taskvars,
TaskfileVars: e.Taskfile.Vars,
Expansions: e.Taskfile.Expansions,
Logger: e.Logger,
}
case version.IsV23(v):
return fmt.Errorf(`task: Taskfile versions greater than v2.3 not implemented in the version of Task`)
Color: e.Color,
}
if !version.IsV21(v) && e.Taskfile.Output != "" {
v, err := strconv.ParseFloat(e.Taskfile.Version, 64)
if err != nil {
return fmt.Errorf(`task: Could not parse taskfile version "%s": %v`, e.Taskfile.Version, err)
}
if v < 2 {
return fmt.Errorf(`task: Taskfile versions prior to v2 are not supported anymore`)
}
// consider as equal to the greater version if round
if v == 2.0 {
v = 2.6
}
if v > 3.0 {
return fmt.Errorf(`task: Taskfile versions greater than v3.0 not implemented in the version of Task`)
}
// Color available only on v3
if v < 3 {
e.Logger.Color = false
}
e.Compiler = &compilerv2.CompilerV2{
Dir: e.Dir,
Taskvars: e.taskvars,
TaskfileVars: e.Taskfile.Vars,
Expansions: e.Taskfile.Expansions,
Logger: e.Logger,
}
if v < 2.1 && e.Taskfile.Output != "" {
return fmt.Errorf(`task: Taskfile option "output" is only available starting on Taskfile version v2.1`)
}
if !version.IsV22(v) && len(e.Taskfile.Includes) > 0 {
if v < 2.2 && len(e.Taskfile.Includes) > 0 {
return fmt.Errorf(`task: Including Taskfiles is only available starting on Taskfile version v2.2`)
}
if e.OutputStyle != "" {
e.Taskfile.Output = e.OutputStyle
}
@@ -151,8 +169,16 @@ func (e *Executor) Setup() error {
return fmt.Errorf(`task: output option "%s" not recognized`, e.Taskfile.Output)
}
if !version.IsV21(v) {
err := fmt.Errorf(`task: Taskfile option "ignore_error" is only available starting on Taskfile version v2.1`)
if e.Taskfile.Method == "" {
if v >= 3 {
e.Taskfile.Method = "checksum"
} else {
e.Taskfile.Method = "timestamp"
}
}
if v <= 2.1 {
err := errors.New(`task: Taskfile option "ignore_error" is only available starting on Taskfile version v2.1`)
for _, task := range e.Taskfile.Tasks {
if task.IgnoreError {
@@ -166,9 +192,19 @@ func (e *Executor) Setup() error {
}
}
if v < 2.6 {
for _, task := range e.Taskfile.Tasks {
if len(task.Preconditions) > 0 {
return errors.New(`task: Task option "preconditions" is only available starting on Taskfile version v2.6`)
}
}
}
e.taskCallCount = make(map[string]*int32, len(e.Taskfile.Tasks))
e.mkdirMutexMap = make(map[string]*sync.Mutex, len(e.Taskfile.Tasks))
for k := range e.Taskfile.Tasks {
e.taskCallCount[k] = new(int32)
e.mkdirMutexMap[k] = &sync.Mutex{}
}
return nil
}
@@ -188,26 +224,36 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
}
if !e.Force {
preCondMet, err := e.areTaskPreconditionsMet(ctx, t)
if err != nil {
return err
}
upToDate, err := e.isTaskUpToDate(ctx, t)
if err != nil {
return err
}
if upToDate {
if upToDate && preCondMet {
if !e.Silent {
e.Logger.Errf(`task: Task "%s" is up to date`, t.Task)
e.Logger.Errf(logger.Magenta, `task: Task "%s" is up to date`, t.Task)
}
return nil
}
}
if err := e.mkdir(t); err != nil {
e.Logger.Errf(logger.Red, "task: cannot make directory %q: %v", t.Dir, err)
}
for i := range t.Cmds {
if err := e.runCommand(ctx, t, call, i); err != nil {
if err2 := e.statusOnError(t); err2 != nil {
e.Logger.VerboseErrf("task: error cleaning status on error: %v", err2)
e.Logger.VerboseErrf(logger.Yellow, "task: error cleaning status on error: %v", err2)
}
if execext.IsExitError(err) && t.IgnoreError {
e.Logger.VerboseErrf("task: task error ignored: %v", err)
e.Logger.VerboseErrf(logger.Yellow, "task: task error ignored: %v", err)
continue
}
@@ -217,6 +263,23 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
return nil
}
func (e *Executor) mkdir(t *taskfile.Task) error {
if t.Dir == "" {
return nil
}
mutex := e.mkdirMutexMap[t.Task]
mutex.Lock()
defer mutex.Unlock()
if _, err := os.Stat(t.Dir); os.IsNotExist(err) {
if err := os.MkdirAll(t.Dir, 0755); err != nil {
return err
}
}
return nil
}
func (e *Executor) runDeps(ctx context.Context, t *taskfile.Task) error {
g, ctx := errgroup.WithContext(ctx)
@@ -224,7 +287,11 @@ func (e *Executor) runDeps(ctx context.Context, t *taskfile.Task) error {
d := d
g.Go(func() error {
return e.RunTask(ctx, taskfile.Call{Task: d.Task, Vars: d.Vars})
err := e.RunTask(ctx, taskfile.Call{Task: d.Task, Vars: d.Vars})
if err != nil {
return err
}
return nil
})
}
@@ -236,10 +303,14 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi
switch {
case cmd.Task != "":
return e.RunTask(ctx, taskfile.Call{Task: cmd.Task, Vars: cmd.Vars})
err := e.RunTask(ctx, taskfile.Call{Task: cmd.Task, Vars: cmd.Vars})
if err != nil {
return err
}
return nil
case cmd.Cmd != "":
if e.Verbose || (!cmd.Silent && !t.Silent && !e.Silent) {
e.Logger.Errf(cmd.Cmd)
e.Logger.Errf(logger.Green, "task: %s", cmd.Cmd)
}
if e.Dry {
@@ -270,7 +341,7 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi
Stderr: stdErr,
})
if execext.IsExitError(err) && cmd.IgnoreError {
e.Logger.VerboseErrf("task: command error ignored: %v", err)
e.Logger.VerboseErrf(logger.Yellow, "task: command error ignored: %v", err)
return nil
}
return err
@@ -285,8 +356,10 @@ func getEnviron(t *taskfile.Task) []string {
}
environ := os.Environ()
for k, v := range t.Env.ToStringMap() {
environ = append(environ, fmt.Sprintf("%s=%s", k, v))
for k, v := range t.Env.ToCacheMap() {
if s, ok := v.(string); ok {
environ = append(environ, fmt.Sprintf("%s=%s", k, s))
}
}
return environ
}

View File

@@ -7,13 +7,13 @@ import (
"io/ioutil"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"github.com/go-task/task/v2"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/mitchellh/go-homedir"
"github.com/stretchr/testify/assert"
)
@@ -69,43 +69,6 @@ func TestEnv(t *testing.T) {
tt.Run(t)
}
func TestVarsV1(t *testing.T) {
tt := fileContentTest{
Dir: "testdata/vars/v1",
Target: "default",
TrimSpace: true,
Files: map[string]string{
// hello task:
"foo.txt": "foo",
"bar.txt": "bar",
"baz.txt": "baz",
"tmpl_foo.txt": "foo",
"tmpl_bar.txt": "<no value>",
"tmpl_foo2.txt": "foo2",
"tmpl_bar2.txt": "bar2",
"shtmpl_foo.txt": "foo",
"shtmpl_foo2.txt": "foo2",
"nestedtmpl_foo.txt": "{{.FOO}}",
"nestedtmpl_foo2.txt": "foo2",
"foo2.txt": "foo2",
"bar2.txt": "bar2",
"baz2.txt": "baz2",
"tmpl2_foo.txt": "<no value>",
"tmpl2_foo2.txt": "foo2",
"tmpl2_bar.txt": "<no value>",
"tmpl2_bar2.txt": "<no value>",
"shtmpl2_foo.txt": "<no value>",
"shtmpl2_foo2.txt": "foo2",
"nestedtmpl2_foo2.txt": "{{.FOO2}}",
"override.txt": "bar",
},
}
tt.Run(t)
// Ensure identical results when running hello task directly.
tt.Target = "hello"
tt.Run(t)
}
func TestVarsV2(t *testing.T) {
tt := fileContentTest{
Dir: "testdata/vars/v2",
@@ -144,7 +107,7 @@ func TestVarsV2(t *testing.T) {
}
func TestMultilineVars(t *testing.T) {
for _, dir := range []string{"testdata/vars/v1/multiline", "testdata/vars/v2/multiline"} {
for _, dir := range []string{"testdata/vars/v2/multiline"} {
tt := fileContentTest{
Dir: dir,
Target: "default",
@@ -168,7 +131,7 @@ func TestMultilineVars(t *testing.T) {
func TestVarsInvalidTmpl(t *testing.T) {
const (
dir = "testdata/vars/v1"
dir = "testdata/vars/v2"
target = "invalid-var-tmpl"
expectError = "template: :1: unexpected EOF"
)
@@ -236,7 +199,7 @@ func TestDeps(t *testing.T) {
for _, f := range files {
f = filepath.Join(dir, f)
if _, err := os.Stat(f); err != nil {
t.Errorf("File %s should exists", f)
t.Errorf("File %s should exist", f)
}
}
}
@@ -248,7 +211,7 @@ func TestStatus(t *testing.T) {
_ = os.Remove(file)
if _, err := os.Stat(file); err == nil {
t.Errorf("File should not exists: %v", err)
t.Errorf("File should not exist: %v", err)
}
var buff bytes.Buffer
@@ -262,7 +225,7 @@ func TestStatus(t *testing.T) {
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "gen-foo"}))
if _, err := os.Stat(file); err != nil {
t.Errorf("File should exists: %v", err)
t.Errorf("File should exist: %v", err)
}
e.Silent = false
@@ -273,6 +236,47 @@ func TestStatus(t *testing.T) {
}
}
func TestPrecondition(t *testing.T) {
const dir = "testdata/precondition"
var buff bytes.Buffer
e := &task.Executor{
Dir: dir,
Stdout: &buff,
Stderr: &buff,
}
// A precondition that has been met
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "foo"}))
if buff.String() != "" {
t.Errorf("Got Output when none was expected: %s", buff.String())
}
// A precondition that was not met
assert.Error(t, e.Run(context.Background(), taskfile.Call{Task: "impossible"}))
if buff.String() != "task: 1 != 0 obviously!\n" {
t.Errorf("Wrong output message: %s", buff.String())
}
buff.Reset()
// Calling a task with a precondition in a dependency fails the task
assert.Error(t, e.Run(context.Background(), taskfile.Call{Task: "depends_on_impossible"}))
if buff.String() != "task: 1 != 0 obviously!\n" {
t.Errorf("Wrong output message: %s", buff.String())
}
buff.Reset()
// Calling a task with a precondition in a cmd fails the task
assert.Error(t, e.Run(context.Background(), taskfile.Call{Task: "executes_failing_task_as_cmd"}))
if buff.String() != "task: 1 != 0 obviously!\n" {
t.Errorf("Wrong output message: %s", buff.String())
}
buff.Reset()
}
func TestGenerates(t *testing.T) {
const (
srcTask = "sub/src.txt"
@@ -290,7 +294,7 @@ func TestGenerates(t *testing.T) {
path := filepath.Join(dir, task)
_ = os.Remove(path)
if _, err := os.Stat(path); err == nil {
t.Errorf("File should not exists: %v", err)
t.Errorf("File should not exist: %v", err)
}
}
@@ -311,10 +315,10 @@ func TestGenerates(t *testing.T) {
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: theTask}))
if _, err := os.Stat(srcFile); err != nil {
t.Errorf("File should exists: %v", err)
t.Errorf("File should exist: %v", err)
}
if _, err := os.Stat(destFile); err != nil {
t.Errorf("File should exists: %v", err)
t.Errorf("File should exist: %v", err)
}
// Ensure task was not incorrectly found to be up-to-date on first run.
if buff.String() == upToDate {
@@ -365,13 +369,41 @@ func TestStatusChecksum(t *testing.T) {
assert.Equal(t, `task: Task "build" is up to date`+"\n", buff.String())
}
func TestStatusVariables(t *testing.T) {
const dir = "testdata/status_vars"
_ = os.RemoveAll(filepath.Join(dir, ".task"))
_ = os.Remove(filepath.Join(dir, "generated.txt"))
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Stdout: &buff,
Stderr: &buff,
Silent: false,
Verbose: true,
}
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "build"}))
assert.Contains(t, buff.String(), "d41d8cd98f00b204e9800998ecf8427e")
inf, err := os.Stat(filepath.Join(dir, "source.txt"))
assert.NoError(t, err)
ts := fmt.Sprintf("%d", inf.ModTime().Unix())
tf := fmt.Sprintf("%s", inf.ModTime())
assert.Contains(t, buff.String(), ts)
assert.Contains(t, buff.String(), tf)
}
func TestInit(t *testing.T) {
const dir = "testdata/init"
var file = filepath.Join(dir, "Taskfile.yml")
_ = os.Remove(file)
if _, err := os.Stat(file); err == nil {
t.Errorf("Taskfile.yml should not exists")
t.Errorf("Taskfile.yml should not exist")
}
if err := task.InitTaskfile(ioutil.Discard, dir); err != nil {
@@ -379,7 +411,7 @@ func TestInit(t *testing.T) {
}
if _, err := os.Stat(file); err != nil {
t.Errorf("Taskfile.yml should exists")
t.Errorf("Taskfile.yml should exist")
}
}
@@ -400,7 +432,6 @@ func TestTaskVersion(t *testing.T) {
Dir string
Version string
}{
{"testdata/version/v1", "1"},
{"testdata/version/v2", "2"},
}
@@ -437,7 +468,7 @@ func TestTaskIgnoreErrors(t *testing.T) {
func TestExpand(t *testing.T) {
const dir = "testdata/expand"
home, err := homedir.Dir()
home, err := os.UserHomeDir()
if err != nil {
t.Errorf("Couldn't get $HOME: %v", err)
}
@@ -470,7 +501,7 @@ func TestDry(t *testing.T) {
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "build"}))
assert.Equal(t, "touch file.txt", strings.TrimSpace(buff.String()))
assert.Equal(t, "task: touch file.txt", strings.TrimSpace(buff.String()))
if _, err := os.Stat(file); err == nil {
t.Errorf("File should not exist %s", file)
}
@@ -567,11 +598,88 @@ func TestSummary(t *testing.T) {
}
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "task-with-summary"}, taskfile.Call{Task: "other-task-with-summary"}))
assert.Equal(t, readTestFixture(t, dir, "task-with-summary.txt"), buff.String())
data, err := ioutil.ReadFile(filepath.Join(dir, "task-with-summary.txt"))
assert.NoError(t, err)
expectedOutput := string(data)
if runtime.GOOS == "windows" {
expectedOutput = strings.Replace(expectedOutput, "\r\n", "\n", -1)
}
assert.Equal(t, expectedOutput, buff.String())
}
func readTestFixture(t *testing.T, dir string, file string) string {
b, err := ioutil.ReadFile(dir + "/" + file)
assert.NoError(t, err, "error reading text fixture")
return string(b)
func TestWhenNoDirAttributeItRunsInSameDirAsTaskfile(t *testing.T) {
const expected = "dir"
const dir = "testdata/" + expected
var out bytes.Buffer
e := &task.Executor{
Dir: dir,
Stdout: &out,
Stderr: &out,
}
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "whereami"}))
// got should be the "dir" part of "testdata/dir"
got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
assert.Equal(t, expected, got, "Mismatch in the working directory")
}
func TestWhenDirAttributeAndDirExistsItRunsInThatDir(t *testing.T) {
const expected = "exists"
const dir = "testdata/dir/explicit_exists"
var out bytes.Buffer
e := &task.Executor{
Dir: dir,
Stdout: &out,
Stderr: &out,
}
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "whereami"}))
got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
assert.Equal(t, expected, got, "Mismatch in the working directory")
}
func TestWhenDirAttributeItCreatesMissingAndRunsInThatDir(t *testing.T) {
const expected = "createme"
const dir = "testdata/dir/explicit_doesnt_exist/"
const toBeCreated = dir + expected
const target = "whereami"
var out bytes.Buffer
e := &task.Executor{
Dir: dir,
Stdout: &out,
Stderr: &out,
}
// Ensure that the directory to be created doesn't actually exist.
_ = os.RemoveAll(toBeCreated)
if _, err := os.Stat(toBeCreated); err == nil {
t.Errorf("Directory should not exist: %v", err)
}
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: target}))
got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
assert.Equal(t, expected, got, "Mismatch in the working directory")
// Clean-up after ourselves only if no error.
_ = os.RemoveAll(toBeCreated)
}
func TestDisplaysErrorOnUnsupportedVersion(t *testing.T) {
e := task.Executor{
Dir: "testdata/version/v1",
Stdout: ioutil.Discard,
Stderr: ioutil.Discard,
}
err := e.Setup()
assert.Error(t, err)
assert.Equal(t, "task: Taskfile versions prior to v2 are not supported anymore", err.Error())
}

View File

@@ -1,9 +1,12 @@
build:
cmds:
- cp ./source.txt ./generated.txt
sources:
- ./**/glob-with-inexistent-file.txt
- ./source.txt
generates:
- ./generated.txt
method: checksum
version: '2'
tasks:
build:
cmds:
- cp ./source.txt ./generated.txt
sources:
- ./**/glob-with-inexistent-file.txt
- ./source.txt
generates:
- ./generated.txt
method: checksum

View File

@@ -1,7 +1,10 @@
task-1:
deps:
- task: task-2
version: '2'
task-2:
deps:
- task: task-1
tasks:
task-1:
deps:
- task: task-2
task-2:
deps:
- task: task-1

View File

@@ -1,53 +1,56 @@
default:
deps: [d1, d2, d3]
version: '2'
d1:
deps: [d11, d12, d13]
cmds:
- echo 'Text' > d1.txt
tasks:
default:
deps: [d1, d2, d3]
d2:
deps: [d21, d22, d23]
cmds:
- echo 'Text' > d2.txt
d1:
deps: [d11, d12, d13]
cmds:
- echo 'Text' > d1.txt
d3:
deps: [d31, d32, d33]
cmds:
- echo 'Text' > d3.txt
d2:
deps: [d21, d22, d23]
cmds:
- echo 'Text' > d2.txt
d11:
cmds:
- echo 'Text' > d11.txt
d3:
deps: [d31, d32, d33]
cmds:
- echo 'Text' > d3.txt
d12:
cmds:
- echo 'Text' > d12.txt
d11:
cmds:
- echo 'Text' > d11.txt
d13:
cmds:
- echo 'Text' > d13.txt
d12:
cmds:
- echo 'Text' > d12.txt
d21:
cmds:
- echo 'Text' > d21.txt
d13:
cmds:
- echo 'Text' > d13.txt
d22:
cmds:
- echo 'Text' > d22.txt
d21:
cmds:
- echo 'Text' > d21.txt
d23:
cmds:
- echo 'Text' > d23.txt
d22:
cmds:
- echo 'Text' > d22.txt
d31:
cmds:
- echo 'Text' > d31.txt
d23:
cmds:
- echo 'Text' > d23.txt
d32:
cmds:
- echo 'Text' > d32.txt
d31:
cmds:
- echo 'Text' > d31.txt
d33:
cmds:
- echo 'Text' > d33.txt
d32:
cmds:
- echo 'Text' > d32.txt
d33:
cmds:
- echo 'Text' > d33.txt

7
testdata/dir/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,7 @@
version: '2'
tasks:
whereami:
cmds:
- pwd
silent: true

View File

@@ -0,0 +1,8 @@
version: '2'
tasks:
whereami:
dir: createme
cmds:
- pwd
silent: true

View File

@@ -0,0 +1,8 @@
version: '2'
tasks:
whereami:
dir: exists
cmds:
- pwd
silent: true

View File

View File

@@ -1,41 +1,48 @@
abs.txt:
desc: generates dest file based on absolute paths
deps:
- sub/src.txt
dir: sub
cmds:
- cat src.txt > '{{.BUILD_DIR}}/abs.txt'
sources:
- src.txt
generates:
- "{{.BUILD_DIR}}/abs.txt"
version: '2'
rel.txt:
desc: generates dest file based on relative paths
deps:
- sub/src.txt
dir: sub
cmds:
- cat src.txt > '../rel.txt'
sources:
- src.txt
generates:
- "../rel.txt"
tasks:
abs.txt:
desc: generates dest file based on absolute paths
deps:
- sub/src.txt
dir: sub
cmds:
- cat src.txt > '{{.BUILD_DIR}}/abs.txt'
method: timestamp
sources:
- src.txt
generates:
- "{{.BUILD_DIR}}/abs.txt"
sub/src.txt:
desc: generate source file
cmds:
- mkdir -p sub
- echo "hello world" > sub/src.txt
status:
- test -f sub/src.txt
rel.txt:
desc: generates dest file based on relative paths
deps:
- sub/src.txt
dir: sub
cmds:
- cat src.txt > '../rel.txt'
method: timestamp
sources:
- src.txt
generates:
- "../rel.txt"
'my text file.txt':
desc: generate file with spaces in the name
deps: [sub/src.txt]
cmds:
- cat sub/src.txt > 'my text file.txt'
sources:
- sub/src.txt
generates:
- 'my text file.txt'
sub/src.txt:
desc: generate source file
cmds:
- mkdir -p sub
- echo "hello world" > sub/src.txt
method: timestamp
status:
- test -f sub/src.txt
'my text file.txt':
desc: generate file with spaces in the name
deps: [sub/src.txt]
cmds:
- cat sub/src.txt > 'my text file.txt'
method: timestamp
sources:
- sub/src.txt
generates:
- 'my text file.txt'

View File

@@ -1,37 +1,40 @@
default:
vars:
SPANISH: ¡Holla mundo!
PORTUGUESE: "{{.PORTUGUESE_HELLO_WORLD}}"
GERMAN: "Welt!"
deps:
- task: write-file
vars: {CONTENT: Dependence1, FILE: dep1.txt}
- task: write-file
vars: {CONTENT: Dependence2, FILE: dep2.txt}
- task: write-file
vars: {CONTENT: "{{.SPANISH|replace \"mundo\" \"dependencia\"}}", FILE: spanish-dep.txt}
cmds:
- task: write-file
vars: {CONTENT: Hello, FILE: hello.txt}
- task: write-file
vars: {CONTENT: "$echo 'World'", FILE: world.txt}
- task: write-file
vars: {CONTENT: "!", FILE: exclamation.txt}
- task: write-file
vars: {CONTENT: "{{.SPANISH}}", FILE: spanish.txt}
- task: write-file
vars: {CONTENT: "{{.PORTUGUESE}}", FILE: portuguese.txt}
- task: write-file
vars: {CONTENT: "{{.GERMAN}}", FILE: german.txt}
- task: non-default
version: '2'
write-file:
cmds:
- echo {{.CONTENT}} > {{.FILE}}
tasks:
default:
vars:
SPANISH: ¡Holla mundo!
PORTUGUESE: "{{.PORTUGUESE_HELLO_WORLD}}"
GERMAN: "Welt!"
deps:
- task: write-file
vars: {CONTENT: Dependence1, FILE: dep1.txt}
- task: write-file
vars: {CONTENT: Dependence2, FILE: dep2.txt}
- task: write-file
vars: {CONTENT: "{{.SPANISH|replace \"mundo\" \"dependencia\"}}", FILE: spanish-dep.txt}
cmds:
- task: write-file
vars: {CONTENT: Hello, FILE: hello.txt}
- task: write-file
vars: {CONTENT: "$echo 'World'", FILE: world.txt}
- task: write-file
vars: {CONTENT: "!", FILE: exclamation.txt}
- task: write-file
vars: {CONTENT: "{{.SPANISH}}", FILE: spanish.txt}
- task: write-file
vars: {CONTENT: "{{.PORTUGUESE}}", FILE: portuguese.txt}
- task: write-file
vars: {CONTENT: "{{.GERMAN}}", FILE: german.txt}
- task: non-default
non-default:
vars:
PORTUGUESE: "{{.PORTUGUESE_HELLO_WORLD}}"
cmds:
- task: write-file
vars: {CONTENT: "{{.PORTUGUESE}}", FILE: portuguese2.txt}
write-file:
cmds:
- echo {{.CONTENT}} > {{.FILE}}
non-default:
vars:
PORTUGUESE: "{{.PORTUGUESE_HELLO_WORLD}}"
cmds:
- task: write-file
vars: {CONTENT: "{{.PORTUGUESE}}", FILE: portuguese2.txt}

19
testdata/precondition/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
version: '2'
tasks:
foo:
preconditions:
- test -f foo.txt
impossible:
preconditions:
- sh: "[ 1 = 0 ]"
msg: "1 != 0 obviously!"
depends_on_impossible:
deps:
- impossible
executes_failing_task_as_cmd:
cmds:
- task: impossible

0
testdata/precondition/foo.txt vendored Normal file
View File

View File

@@ -1,5 +1,8 @@
gen-foo:
cmds:
- touch foo.txt
status:
- test -f foo.txt
version: '2'
tasks:
gen-foo:
cmds:
- touch foo.txt
status:
- test -f foo.txt

1
testdata/status_vars/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
generated.txt

10
testdata/status_vars/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,10 @@
version: '3'
tasks:
build:
sources:
- ./source.txt
status:
- echo "{{.CHECKSUM}}"
- echo '{{.TIMESTAMP.Unix}}'
- echo '{{.TIMESTAMP}}'

1
testdata/status_vars/source.txt vendored Normal file
View File

@@ -0,0 +1 @@
Hello, World!

View File

@@ -1,4 +1,4 @@
version: 2
version: '2'
tasks:
task-with-summary:

View File

@@ -1 +0,0 @@
*.txt

View File

@@ -1,48 +0,0 @@
default:
deps: [hello]
hello:
cmds:
- echo {{.FOO}} > foo.txt
- echo {{.BAR}} > bar.txt
- echo {{.BAZ}} > baz.txt
- echo '{{.TMPL_FOO}}' > tmpl_foo.txt
- echo '{{.TMPL_BAR}}' > tmpl_bar.txt
- echo '{{.TMPL_FOO2}}' > tmpl_foo2.txt
- echo '{{.TMPL_BAR2}}' > tmpl_bar2.txt
- echo '{{.SHTMPL_FOO}}' > shtmpl_foo.txt
- echo '{{.SHTMPL_FOO2}}' > shtmpl_foo2.txt
- echo '{{.NESTEDTMPL_FOO}}' > nestedtmpl_foo.txt
- echo '{{.NESTEDTMPL_FOO2}}' > nestedtmpl_foo2.txt
- echo {{.FOO2}} > foo2.txt
- echo {{.BAR2}} > bar2.txt
- echo {{.BAZ2}} > baz2.txt
- echo '{{.TMPL2_FOO}}' > tmpl2_foo.txt
- echo '{{.TMPL2_BAR}}' > tmpl2_bar.txt
- echo '{{.TMPL2_FOO2}}' > tmpl2_foo2.txt
- echo '{{.TMPL2_BAR2}}' > tmpl2_bar2.txt
- echo '{{.SHTMPL2_FOO}}' > shtmpl2_foo.txt
- echo '{{.SHTMPL2_FOO2}}' > shtmpl2_foo2.txt
- echo '{{.NESTEDTMPL2_FOO2}}' > nestedtmpl2_foo2.txt
- echo {{.OVERRIDE}} > override.txt
vars:
FOO: foo
BAR: $echo bar
BAZ:
sh: echo baz
TMPL_FOO: "{{.FOO}}"
TMPL_BAR: "{{.BAR}}"
TMPL_FOO2: "{{.FOO2}}"
TMPL_BAR2: "{{.BAR2}}"
SHTMPL_FOO:
sh: "echo '{{.FOO}}'"
SHTMPL_FOO2:
sh: "echo '{{.FOO2}}'"
NESTEDTMPL_FOO: "{{.TMPL_FOO}}"
NESTEDTMPL_FOO2: "{{.TMPL2_FOO2}}"
OVERRIDE: "bar"
invalid-var-tmpl:
vars:
CHARS: "abcd"
INVALID: "{{range .CHARS}}no end"

View File

@@ -1,12 +0,0 @@
FOO2: foo2
BAR2: $echo bar2
BAZ2:
sh: echo baz2
TMPL2_FOO: "{{.FOO}}"
TMPL2_BAR: "{{.BAR}}"
TMPL2_FOO2: "{{.FOO2}}"
TMPL2_BAR2: "{{.BAR2}}"
SHTMPL2_FOO2:
sh: "echo '{{.FOO2}}'"
NESTEDTMPL2_FOO2: "{{.TMPL2_FOO2}}"
OVERRIDE: "foo"

View File

@@ -1,43 +0,0 @@
default:
vars:
MULTILINE: "\n\nfoo\n bar\nfoobar\n\nbaz\n\n"
cmds:
- task: file
vars:
CONTENT:
sh: "echo 'foo\nbar'"
FILE: "echo_foobar.txt"
- task: file
vars:
CONTENT:
sh: "echo -n 'foo\nbar'"
FILE: "echo_n_foobar.txt"
- task: file
vars:
CONTENT:
sh: echo -n "{{.MULTILINE}}"
FILE: "echo_n_multiline.txt"
- task: file
vars:
CONTENT: "{{.MULTILINE}}"
FILE: "var_multiline.txt"
- task: file
vars:
CONTENT: "{{.MULTILINE | catLines}}"
FILE: "var_catlines.txt"
- task: enumfile
vars:
LINES: "{{.MULTILINE}}"
FILE: "var_enumfile.txt"
file:
cmds:
- |
cat << EOF > '{{.FILE}}'
{{.CONTENT}}
EOF
enumfile:
cmds:
- |
cat << EOF > '{{.FILE}}'
{{range $i, $line := .LINES| splitLines}}{{$i}}:{{$line}}
{{end}}EOF

View File

@@ -1,9 +1,9 @@
version: 2
version: '2'
tasks:
foo:
cmds:
- echo "Foo"
bar:
cmds:
- echo "Bar"

View File

@@ -2,8 +2,10 @@ package task
import (
"path/filepath"
"strings"
"github.com/go-task/task/v2/internal/execext"
"github.com/go-task/task/v2/internal/status"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/go-task/task/v2/internal/templater"
)
@@ -20,6 +22,7 @@ func (e *Executor) CompiledTask(call taskfile.Call) (*taskfile.Task, error) {
if err != nil {
return nil, err
}
r := templater.Templater{Vars: vars}
new := taskfile.Task{
@@ -27,7 +30,6 @@ func (e *Executor) CompiledTask(call taskfile.Call) (*taskfile.Task, error) {
Desc: r.Replace(origTask.Desc),
Sources: r.ReplaceSlice(origTask.Sources),
Generates: r.ReplaceSlice(origTask.Generates),
Status: r.ReplaceSlice(origTask.Status),
Dir: r.Replace(origTask.Dir),
Vars: nil,
Env: nil,
@@ -84,5 +86,31 @@ func (e *Executor) CompiledTask(call taskfile.Call) (*taskfile.Task, error) {
}
}
if len(origTask.Preconditions) > 0 {
new.Preconditions = make([]*taskfile.Precondition, len(origTask.Preconditions))
for i, precond := range origTask.Preconditions {
new.Preconditions[i] = &taskfile.Precondition{
Sh: r.Replace(precond.Sh),
Msg: r.Replace(precond.Msg),
}
}
}
if len(origTask.Status) > 0 {
for _, checker := range []status.Checker{e.timestampChecker(&new), e.checksumChecker(&new)} {
value, err := checker.Value()
if err != nil {
return nil, err
}
vars[strings.ToUpper(checker.Kind())] = taskfile.Var{Live: value}
}
// Adding new variables, requires us to refresh the templaters
// cache of the the values manually
r.ResetCache()
new.Status = r.ReplaceSlice(origTask.Status)
}
return &new, r.Err()
}

12
vendor/gopkg.in/yaml.v2/.travis.yml generated vendored Normal file
View File

@@ -0,0 +1,12 @@
language: go
go:
- 1.4
- 1.5
- 1.6
- 1.7
- 1.8
- 1.9
- tip
go_import_path: gopkg.in/yaml.v2

201
vendor/gopkg.in/yaml.v2/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -1,17 +1,16 @@
This project is covered by two different licenses: MIT and Apache.
#### MIT License ####
The following files were ported to Go from C files of libyaml, and thus
are still covered by their original MIT license, with the additional
copyright staring in 2011 when the project was ported over:
are still covered by their original copyright and license:
apic.go emitterc.go parserc.go readerc.go scannerc.go
writerc.go yamlh.go yamlprivateh.go
apic.go
emitterc.go
parserc.go
readerc.go
scannerc.go
writerc.go
yamlh.go
yamlprivateh.go
Copyright (c) 2006-2010 Kirill Simonov
Copyright (c) 2006-2011 Kirill Simonov
Copyright (c) 2006 Kirill Simonov
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
@@ -30,21 +29,3 @@ 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.
### Apache License ###
All the remaining project files are covered by the Apache license:
Copyright (c) 2011-2019 Canonical Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -12,23 +12,7 @@ C library to parse and generate YAML data quickly and reliably.
Compatibility
-------------
The yaml package supports most of YAML 1.2, but preserves some behavior
from 1.1 for backwards compatibility.
Specifically, as of v3 of the yaml package:
- YAML 1.1 bools (_yes/no, on/off_) are supported as long as they are being
decoded into a typed bool value. Otherwise they behave as a string. Booleans
in YAML 1.2 are _true/false_ only.
- Octals encode and decode as _0777_ per YAML 1.1, rather than _0o777_
as specified in YAML 1.2, because most parsers still use the old format.
Octals in the _0o777_ format are supported though, so new files work.
- Does not support base-60 floats. These are gone from YAML 1.2, and were
actually never supported by this package as it's clearly a poor choice.
and offers backwards
compatibility with YAML 1.1 in some cases.
1.2, including support for
The yaml package supports most of YAML 1.1 and 1.2, including support for
anchors, tags, map merging, etc. Multi-document unmarshalling is not yet
implemented, and base-60 floats from YAML 1.1 are purposefully not
supported since they're a poor design and are gone in YAML 1.2.
@@ -36,30 +20,29 @@ supported since they're a poor design and are gone in YAML 1.2.
Installation and usage
----------------------
The import path for the package is *gopkg.in/yaml.v3*.
The import path for the package is *gopkg.in/yaml.v2*.
To install it, run:
go get gopkg.in/yaml.v3
go get gopkg.in/yaml.v2
API documentation
-----------------
If opened in a browser, the import path itself leads to the API documentation:
- [https://gopkg.in/yaml.v3](https://gopkg.in/yaml.v3)
* [https://gopkg.in/yaml.v2](https://gopkg.in/yaml.v2)
API stability
-------------
The package API for yaml v3 will remain stable as described in [gopkg.in](https://gopkg.in).
The package API for yaml v2 will remain stable as described in [gopkg.in](https://gopkg.in).
License
-------
The yaml package is licensed under the MIT and Apache License 2.0 licenses.
Please see the LICENSE file for details.
The yaml package is licensed under the Apache License 2.0. Please see the LICENSE file for details.
Example
@@ -72,7 +55,7 @@ import (
"fmt"
"log"
"gopkg.in/yaml.v3"
"gopkg.in/yaml.v2"
)
var data = `

View File

@@ -1,25 +1,3 @@
//
// Copyright (c) 2011-2019 Canonical Ltd
// Copyright (c) 2006-2010 Kirill Simonov
//
// 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.
package yaml
import (
@@ -160,7 +138,7 @@ func yaml_emitter_set_canonical(emitter *yaml_emitter_t, canonical bool) {
emitter.canonical = canonical
}
// Set the indentation increment.
//// Set the indentation increment.
func yaml_emitter_set_indent(emitter *yaml_emitter_t, indent int) {
if indent < 2 || indent > 9 {
indent = 2
@@ -310,14 +288,29 @@ func yaml_document_end_event_initialize(event *yaml_event_t, implicit bool) {
}
}
// Create ALIAS.
func yaml_alias_event_initialize(event *yaml_event_t, anchor []byte) bool {
*event = yaml_event_t{
typ: yaml_ALIAS_EVENT,
anchor: anchor,
}
return true
}
///*
// * Create ALIAS.
// */
//
//YAML_DECLARE(int)
//yaml_alias_event_initialize(event *yaml_event_t, anchor *yaml_char_t)
//{
// mark yaml_mark_t = { 0, 0, 0 }
// anchor_copy *yaml_char_t = NULL
//
// assert(event) // Non-NULL event object is expected.
// assert(anchor) // Non-NULL anchor is expected.
//
// if (!yaml_check_utf8(anchor, strlen((char *)anchor))) return 0
//
// anchor_copy = yaml_strdup(anchor)
// if (!anchor_copy)
// return 0
//
// ALIAS_EVENT_INIT(*event, anchor_copy, mark, mark)
//
// return 1
//}
// Create SCALAR.
func yaml_scalar_event_initialize(event *yaml_event_t, anchor, tag, value []byte, plain_implicit, quoted_implicit bool, style yaml_scalar_style_t) bool {

View File

@@ -1,18 +1,3 @@
//
// Copyright (c) 2011-2019 Canonical Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package yaml
import (
@@ -26,14 +11,33 @@ import (
"time"
)
const (
documentNode = 1 << iota
mappingNode
sequenceNode
scalarNode
aliasNode
)
type node struct {
kind int
line, column int
tag string
// For an alias node, alias holds the resolved alias.
alias *node
value string
implicit bool
children []*node
anchors map[string]*node
}
// ----------------------------------------------------------------------------
// Parser, produces a node tree out of a libyaml event stream.
type parser struct {
parser yaml_parser_t
event yaml_event_t
doc *Node
anchors map[string]*Node
doc *node
doneInit bool
}
@@ -62,7 +66,6 @@ func (p *parser) init() {
if p.doneInit {
return
}
p.anchors = make(map[string]*Node)
p.expect(yaml_STREAM_START_EVENT)
p.doneInit = true
}
@@ -129,14 +132,13 @@ func (p *parser) fail() {
failf("%s%s", where, msg)
}
func (p *parser) anchor(n *Node, anchor []byte) {
func (p *parser) anchor(n *node, anchor []byte) {
if anchor != nil {
n.Anchor = string(anchor)
p.anchors[n.Anchor] = n
p.doc.anchors[string(anchor)] = n
}
}
func (p *parser) parse() *Node {
func (p *parser) parse() *node {
p.init()
switch p.peek() {
case yaml_SCALAR_EVENT:
@@ -157,120 +159,63 @@ func (p *parser) parse() *Node {
}
}
func (p *parser) node(kind Kind, defaultTag, tag, value string) *Node {
var style Style
if tag != "" && tag != "!" {
tag = shortTag(tag)
style = TaggedStyle
} else if defaultTag != "" {
tag = defaultTag
} else if kind == ScalarNode {
tag, _ = resolve("", value)
}
return &Node{
Kind: kind,
Tag: tag,
Value: value,
Style: style,
Line: p.event.start_mark.line + 1,
Column: p.event.start_mark.column + 1,
HeadComment: string(p.event.head_comment),
LineComment: string(p.event.line_comment),
FootComment: string(p.event.foot_comment),
func (p *parser) node(kind int) *node {
return &node{
kind: kind,
line: p.event.start_mark.line,
column: p.event.start_mark.column,
}
}
func (p *parser) parseChild(parent *Node) *Node {
child := p.parse()
parent.Content = append(parent.Content, child)
return child
}
func (p *parser) document() *Node {
n := p.node(DocumentNode, "", "", "")
func (p *parser) document() *node {
n := p.node(documentNode)
n.anchors = make(map[string]*node)
p.doc = n
p.expect(yaml_DOCUMENT_START_EVENT)
p.parseChild(n)
if p.peek() == yaml_DOCUMENT_END_EVENT {
n.FootComment = string(p.event.foot_comment)
}
n.children = append(n.children, p.parse())
p.expect(yaml_DOCUMENT_END_EVENT)
return n
}
func (p *parser) alias() *Node {
n := p.node(AliasNode, "", "", string(p.event.anchor))
n.Alias = p.anchors[n.Value]
if n.Alias == nil {
failf("unknown anchor '%s' referenced", n.Value)
func (p *parser) alias() *node {
n := p.node(aliasNode)
n.value = string(p.event.anchor)
n.alias = p.doc.anchors[n.value]
if n.alias == nil {
failf("unknown anchor '%s' referenced", n.value)
}
p.expect(yaml_ALIAS_EVENT)
return n
}
func (p *parser) scalar() *Node {
var parsedStyle = p.event.scalar_style()
var nodeStyle Style
switch {
case parsedStyle&yaml_DOUBLE_QUOTED_SCALAR_STYLE != 0:
nodeStyle = DoubleQuotedStyle
case parsedStyle&yaml_SINGLE_QUOTED_SCALAR_STYLE != 0:
nodeStyle = SingleQuotedStyle
case parsedStyle&yaml_LITERAL_SCALAR_STYLE != 0:
nodeStyle = LiteralStyle
case parsedStyle&yaml_FOLDED_SCALAR_STYLE != 0:
nodeStyle = FoldedStyle
}
var nodeValue = string(p.event.value)
var nodeTag = string(p.event.tag)
var defaultTag string
if nodeStyle == 0 {
if nodeValue == "<<" {
defaultTag = mergeTag
}
} else {
defaultTag = strTag
}
n := p.node(ScalarNode, defaultTag, nodeTag, nodeValue)
n.Style |= nodeStyle
func (p *parser) scalar() *node {
n := p.node(scalarNode)
n.value = string(p.event.value)
n.tag = string(p.event.tag)
n.implicit = p.event.implicit
p.anchor(n, p.event.anchor)
p.expect(yaml_SCALAR_EVENT)
return n
}
func (p *parser) sequence() *Node {
n := p.node(SequenceNode, seqTag, string(p.event.tag), "")
if p.event.sequence_style()&yaml_FLOW_SEQUENCE_STYLE != 0 {
n.Style |= FlowStyle
}
func (p *parser) sequence() *node {
n := p.node(sequenceNode)
p.anchor(n, p.event.anchor)
p.expect(yaml_SEQUENCE_START_EVENT)
for p.peek() != yaml_SEQUENCE_END_EVENT {
p.parseChild(n)
n.children = append(n.children, p.parse())
}
n.LineComment = string(p.event.line_comment)
n.FootComment = string(p.event.foot_comment)
p.expect(yaml_SEQUENCE_END_EVENT)
return n
}
func (p *parser) mapping() *Node {
n := p.node(MappingNode, mapTag, string(p.event.tag), "")
if p.event.mapping_style()&yaml_FLOW_MAPPING_STYLE != 0 {
n.Style |= FlowStyle
}
func (p *parser) mapping() *node {
n := p.node(mappingNode)
p.anchor(n, p.event.anchor)
p.expect(yaml_MAPPING_START_EVENT)
for p.peek() != yaml_MAPPING_END_EVENT {
k := p.parseChild(n)
v := p.parseChild(n)
if v.FootComment != "" {
k.FootComment = v.FootComment
v.FootComment = ""
}
n.children = append(n.children, p.parse(), p.parse())
}
n.LineComment = string(p.event.line_comment)
n.FootComment = string(p.event.foot_comment)
p.expect(yaml_MAPPING_END_EVENT)
return n
}
@@ -279,60 +224,44 @@ func (p *parser) mapping() *Node {
// Decoder, unmarshals a node into a provided value.
type decoder struct {
doc *Node
aliases map[*Node]bool
doc *node
aliases map[*node]bool
mapType reflect.Type
terrors []string
stringMapType reflect.Type
generalMapType reflect.Type
knownFields bool
uniqueKeys bool
strict bool
}
var (
nodeType = reflect.TypeOf(Node{})
mapItemType = reflect.TypeOf(MapItem{})
durationType = reflect.TypeOf(time.Duration(0))
stringMapType = reflect.TypeOf(map[string]interface{}{})
generalMapType = reflect.TypeOf(map[interface{}]interface{}{})
ifaceType = generalMapType.Elem()
defaultMapType = reflect.TypeOf(map[interface{}]interface{}{})
ifaceType = defaultMapType.Elem()
timeType = reflect.TypeOf(time.Time{})
ptrTimeType = reflect.TypeOf(&time.Time{})
)
func newDecoder() *decoder {
d := &decoder{
stringMapType: stringMapType,
generalMapType: generalMapType,
uniqueKeys: true,
}
d.aliases = make(map[*Node]bool)
func newDecoder(strict bool) *decoder {
d := &decoder{mapType: defaultMapType, strict: strict}
d.aliases = make(map[*node]bool)
return d
}
func (d *decoder) terror(n *Node, tag string, out reflect.Value) {
if n.Tag != "" {
tag = n.Tag
func (d *decoder) terror(n *node, tag string, out reflect.Value) {
if n.tag != "" {
tag = n.tag
}
value := n.Value
if tag != seqTag && tag != mapTag {
value := n.value
if tag != yaml_SEQ_TAG && tag != yaml_MAP_TAG {
if len(value) > 10 {
value = " `" + value[:7] + "...`"
} else {
value = " `" + value + "`"
}
}
d.terrors = append(d.terrors, fmt.Sprintf("line %d: cannot unmarshal %s%s into %s", n.Line, shortTag(tag), value, out.Type()))
d.terrors = append(d.terrors, fmt.Sprintf("line %d: cannot unmarshal %s%s into %s", n.line+1, shortTag(tag), value, out.Type()))
}
func (d *decoder) callUnmarshaler(n *Node, u Unmarshaler) (good bool) {
if err := u.UnmarshalYAML(n); err != nil {
fail(err)
}
return true
}
func (d *decoder) callObsoleteUnmarshaler(n *Node, u obsoleteUnmarshaler) (good bool) {
func (d *decoder) callUnmarshaler(n *node, u Unmarshaler) (good bool) {
terrlen := len(d.terrors)
err := u.UnmarshalYAML(func(v interface{}) (err error) {
defer handleErr(&err)
@@ -361,8 +290,8 @@ func (d *decoder) callObsoleteUnmarshaler(n *Node, u obsoleteUnmarshaler) (good
// its types unmarshalled appropriately.
//
// If n holds a null value, prepare returns before doing anything.
func (d *decoder) prepare(n *Node, out reflect.Value) (newout reflect.Value, unmarshaled, good bool) {
if n.ShortTag() == nullTag {
func (d *decoder) prepare(n *node, out reflect.Value) (newout reflect.Value, unmarshaled, good bool) {
if n.tag == yaml_NULL_TAG || n.kind == scalarNode && n.tag == "" && (n.value == "null" || n.value == "~" || n.value == "" && n.implicit) {
return out, false, false
}
again := true
@@ -376,84 +305,55 @@ func (d *decoder) prepare(n *Node, out reflect.Value) (newout reflect.Value, unm
again = true
}
if out.CanAddr() {
outi := out.Addr().Interface()
if u, ok := outi.(Unmarshaler); ok {
if u, ok := out.Addr().Interface().(Unmarshaler); ok {
good = d.callUnmarshaler(n, u)
return out, true, good
}
if u, ok := outi.(obsoleteUnmarshaler); ok {
good = d.callObsoleteUnmarshaler(n, u)
return out, true, good
}
}
}
return out, false, false
}
func (d *decoder) fieldByIndex(n *Node, v reflect.Value, index []int) (field reflect.Value) {
if n.ShortTag() == nullTag {
return reflect.Value{}
}
for _, num := range index {
for {
if v.Kind() == reflect.Ptr {
if v.IsNil() {
v.Set(reflect.New(v.Type().Elem()))
}
v = v.Elem()
continue
}
break
}
v = v.Field(num)
}
return v
}
func (d *decoder) unmarshal(n *Node, out reflect.Value) (good bool) {
if out.Type() == nodeType {
out.Set(reflect.ValueOf(n).Elem())
return true
}
switch n.Kind {
case DocumentNode:
func (d *decoder) unmarshal(n *node, out reflect.Value) (good bool) {
switch n.kind {
case documentNode:
return d.document(n, out)
case AliasNode:
case aliasNode:
return d.alias(n, out)
}
out, unmarshaled, good := d.prepare(n, out)
if unmarshaled {
return good
}
switch n.Kind {
case ScalarNode:
switch n.kind {
case scalarNode:
good = d.scalar(n, out)
case MappingNode:
case mappingNode:
good = d.mapping(n, out)
case SequenceNode:
case sequenceNode:
good = d.sequence(n, out)
default:
panic("internal error: unknown node kind: " + strconv.Itoa(int(n.Kind)))
panic("internal error: unknown node kind: " + strconv.Itoa(n.kind))
}
return good
}
func (d *decoder) document(n *Node, out reflect.Value) (good bool) {
if len(n.Content) == 1 {
func (d *decoder) document(n *node, out reflect.Value) (good bool) {
if len(n.children) == 1 {
d.doc = n
d.unmarshal(n.Content[0], out)
d.unmarshal(n.children[0], out)
return true
}
return false
}
func (d *decoder) alias(n *Node, out reflect.Value) (good bool) {
func (d *decoder) alias(n *node, out reflect.Value) (good bool) {
if d.aliases[n] {
// TODO this could actually be allowed in some circumstances.
failf("anchor '%s' value contains itself", n.Value)
failf("anchor '%s' value contains itself", n.value)
}
d.aliases[n] = true
good = d.unmarshal(n.Alias, out)
good = d.unmarshal(n.alias, out)
delete(d.aliases, n)
return good
}
@@ -466,15 +366,15 @@ func resetMap(out reflect.Value) {
}
}
func (d *decoder) scalar(n *Node, out reflect.Value) bool {
func (d *decoder) scalar(n *node, out reflect.Value) bool {
var tag string
var resolved interface{}
if n.indicatedString() {
tag = strTag
resolved = n.Value
if n.tag == "" && !n.implicit {
tag = yaml_STR_TAG
resolved = n.value
} else {
tag, resolved = resolve(n.Tag, n.Value)
if tag == binaryTag {
tag, resolved = resolve(n.tag, n.value)
if tag == yaml_BINARY_TAG {
data, err := base64.StdEncoding.DecodeString(resolved.(string))
if err != nil {
failf("!!binary value contains invalid base64 data")
@@ -483,14 +383,12 @@ func (d *decoder) scalar(n *Node, out reflect.Value) bool {
}
}
if resolved == nil {
if out.CanAddr() {
switch out.Kind() {
case reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice:
out.Set(reflect.Zero(out.Type()))
return true
}
if out.Kind() == reflect.Map && !out.CanAddr() {
resetMap(out)
} else {
out.Set(reflect.Zero(out.Type()))
}
return false
return true
}
if resolvedv := reflect.ValueOf(resolved); out.Type() == resolvedv.Type() {
// We've resolved to exactly the type we want, so use that.
@@ -503,13 +401,13 @@ func (d *decoder) scalar(n *Node, out reflect.Value) bool {
u, ok := out.Addr().Interface().(encoding.TextUnmarshaler)
if ok {
var text []byte
if tag == binaryTag {
if tag == yaml_BINARY_TAG {
text = []byte(resolved.(string))
} else {
// We let any value be unmarshaled into TextUnmarshaler.
// That might be more lax than we'd like, but the
// TextUnmarshaler itself should bowl out any dubious values.
text = []byte(n.Value)
text = []byte(n.value)
}
err := u.UnmarshalText(text)
if err != nil {
@@ -520,37 +418,47 @@ func (d *decoder) scalar(n *Node, out reflect.Value) bool {
}
switch out.Kind() {
case reflect.String:
if tag == binaryTag {
if tag == yaml_BINARY_TAG {
out.SetString(resolved.(string))
return true
}
out.SetString(n.Value)
return true
if resolved != nil {
out.SetString(n.value)
return true
}
case reflect.Interface:
out.Set(reflect.ValueOf(resolved))
if resolved == nil {
out.Set(reflect.Zero(out.Type()))
} else if tag == yaml_TIMESTAMP_TAG {
// It looks like a timestamp but for backward compatibility
// reasons we set it as a string, so that code that unmarshals
// timestamp-like values into interface{} will continue to
// see a string and not a time.Time.
// TODO(v3) Drop this.
out.Set(reflect.ValueOf(n.value))
} else {
out.Set(reflect.ValueOf(resolved))
}
return true
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
// This used to work in v2, but it's very unfriendly.
isDuration := out.Type() == durationType
switch resolved := resolved.(type) {
case int:
if !isDuration && !out.OverflowInt(int64(resolved)) {
if !out.OverflowInt(int64(resolved)) {
out.SetInt(int64(resolved))
return true
}
case int64:
if !isDuration && !out.OverflowInt(resolved) {
if !out.OverflowInt(resolved) {
out.SetInt(resolved)
return true
}
case uint64:
if !isDuration && resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) {
if resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) {
out.SetInt(int64(resolved))
return true
}
case float64:
if !isDuration && resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) {
if resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) {
out.SetInt(int64(resolved))
return true
}
@@ -591,17 +499,6 @@ func (d *decoder) scalar(n *Node, out reflect.Value) bool {
case bool:
out.SetBool(resolved)
return true
case string:
// This offers some compatibility with the 1.1 spec (https://yaml.org/type/bool.html).
// It only works if explicitly attempting to unmarshal into a typed bool value.
switch resolved {
case "y", "Y", "yes", "Yes", "YES", "on", "On", "ON":
out.SetBool(true)
return true
case "n", "N", "no", "No", "NO", "off", "Off", "OFF":
out.SetBool(false)
return true
}
}
case reflect.Float32, reflect.Float64:
switch resolved := resolved.(type) {
@@ -624,7 +521,13 @@ func (d *decoder) scalar(n *Node, out reflect.Value) bool {
return true
}
case reflect.Ptr:
panic("yaml internal error: please report the issue")
if out.Type().Elem() == reflect.TypeOf(resolved) {
// TODO DOes this make sense? When is out a Ptr except when decoding a nil value?
elem := reflect.New(out.Type().Elem())
elem.Elem().Set(reflect.ValueOf(resolved))
out.Set(elem)
return true
}
}
d.terror(n, tag, out)
return false
@@ -637,8 +540,8 @@ func settableValueOf(i interface{}) reflect.Value {
return sv
}
func (d *decoder) sequence(n *Node, out reflect.Value) (good bool) {
l := len(n.Content)
func (d *decoder) sequence(n *node, out reflect.Value) (good bool) {
l := len(n.children)
var iface reflect.Value
switch out.Kind() {
@@ -653,7 +556,7 @@ func (d *decoder) sequence(n *Node, out reflect.Value) (good bool) {
iface = out
out = settableValueOf(make([]interface{}, l))
default:
d.terror(n, seqTag, out)
d.terror(n, yaml_SEQ_TAG, out)
return false
}
et := out.Type().Elem()
@@ -661,7 +564,7 @@ func (d *decoder) sequence(n *Node, out reflect.Value) (good bool) {
j := 0
for i := 0; i < l; i++ {
e := reflect.New(et).Elem()
if ok := d.unmarshal(n.Content[i], e); ok {
if ok := d.unmarshal(n.children[i], e); ok {
out.Index(j).Set(e)
j++
}
@@ -675,65 +578,51 @@ func (d *decoder) sequence(n *Node, out reflect.Value) (good bool) {
return true
}
func (d *decoder) mapping(n *Node, out reflect.Value) (good bool) {
l := len(n.Content)
if d.uniqueKeys {
nerrs := len(d.terrors)
for i := 0; i < l; i += 2 {
ni := n.Content[i]
for j := i + 2; j < l; j += 2 {
nj := n.Content[j]
if ni.Kind == nj.Kind && ni.Value == nj.Value {
d.terrors = append(d.terrors, fmt.Sprintf("line %d: mapping key %#v already defined at line %d", nj.Line, nj.Value, ni.Line))
}
}
}
if len(d.terrors) > nerrs {
return false
}
}
func (d *decoder) mapping(n *node, out reflect.Value) (good bool) {
switch out.Kind() {
case reflect.Struct:
return d.mappingStruct(n, out)
case reflect.Slice:
return d.mappingSlice(n, out)
case reflect.Map:
// okay
case reflect.Interface:
iface := out
if isStringMap(n) {
out = reflect.MakeMap(d.stringMapType)
if d.mapType.Kind() == reflect.Map {
iface := out
out = reflect.MakeMap(d.mapType)
iface.Set(out)
} else {
out = reflect.MakeMap(d.generalMapType)
slicev := reflect.New(d.mapType).Elem()
if !d.mappingSlice(n, slicev) {
return false
}
out.Set(slicev)
return true
}
iface.Set(out)
default:
d.terror(n, mapTag, out)
d.terror(n, yaml_MAP_TAG, out)
return false
}
outt := out.Type()
kt := outt.Key()
et := outt.Elem()
stringMapType := d.stringMapType
generalMapType := d.generalMapType
if outt.Elem() == ifaceType {
if outt.Key().Kind() == reflect.String {
d.stringMapType = outt
} else if outt.Key() == ifaceType {
d.generalMapType = outt
}
mapType := d.mapType
if outt.Key() == ifaceType && outt.Elem() == ifaceType {
d.mapType = outt
}
if out.IsNil() {
out.Set(reflect.MakeMap(outt))
}
l := len(n.children)
for i := 0; i < l; i += 2 {
if isMerge(n.Content[i]) {
d.merge(n.Content[i+1], out)
if isMerge(n.children[i]) {
d.merge(n.children[i+1], out)
continue
}
k := reflect.New(kt).Elem()
if d.unmarshal(n.Content[i], k) {
if d.unmarshal(n.children[i], k) {
kkind := k.Kind()
if kkind == reflect.Interface {
kkind = k.Elem().Kind()
@@ -742,34 +631,61 @@ func (d *decoder) mapping(n *Node, out reflect.Value) (good bool) {
failf("invalid map key: %#v", k.Interface())
}
e := reflect.New(et).Elem()
if d.unmarshal(n.Content[i+1], e) {
out.SetMapIndex(k, e)
if d.unmarshal(n.children[i+1], e) {
d.setMapIndex(n.children[i+1], out, k, e)
}
}
}
d.stringMapType = stringMapType
d.generalMapType = generalMapType
d.mapType = mapType
return true
}
func isStringMap(n *Node) bool {
if n.Kind != MappingNode {
func (d *decoder) setMapIndex(n *node, out, k, v reflect.Value) {
if d.strict && out.MapIndex(k) != zeroValue {
d.terrors = append(d.terrors, fmt.Sprintf("line %d: key %#v already set in map", n.line+1, k.Interface()))
return
}
out.SetMapIndex(k, v)
}
func (d *decoder) mappingSlice(n *node, out reflect.Value) (good bool) {
outt := out.Type()
if outt.Elem() != mapItemType {
d.terror(n, yaml_MAP_TAG, out)
return false
}
l := len(n.Content)
for i := 0; i < l; i++ {
if n.Content[i].ShortTag() != strTag {
return false
mapType := d.mapType
d.mapType = outt
var slice []MapItem
var l = len(n.children)
for i := 0; i < l; i += 2 {
if isMerge(n.children[i]) {
d.merge(n.children[i+1], out)
continue
}
item := MapItem{}
k := reflect.ValueOf(&item.Key).Elem()
if d.unmarshal(n.children[i], k) {
v := reflect.ValueOf(&item.Value).Elem()
if d.unmarshal(n.children[i+1], v) {
slice = append(slice, item)
}
}
}
out.Set(reflect.ValueOf(slice))
d.mapType = mapType
return true
}
func (d *decoder) mappingStruct(n *Node, out reflect.Value) (good bool) {
func (d *decoder) mappingStruct(n *node, out reflect.Value) (good bool) {
sinfo, err := getStructInfo(out.Type())
if err != nil {
panic(err)
}
name := settableValueOf("")
l := len(n.children)
var inlineMap reflect.Value
var elemType reflect.Type
@@ -779,30 +695,23 @@ func (d *decoder) mappingStruct(n *Node, out reflect.Value) (good bool) {
elemType = inlineMap.Type().Elem()
}
for _, index := range sinfo.InlineUnmarshalers {
field := d.fieldByIndex(n, out, index)
d.prepare(n, field)
}
var doneFields []bool
if d.uniqueKeys {
if d.strict {
doneFields = make([]bool, len(sinfo.FieldsList))
}
name := settableValueOf("")
l := len(n.Content)
for i := 0; i < l; i += 2 {
ni := n.Content[i]
ni := n.children[i]
if isMerge(ni) {
d.merge(n.Content[i+1], out)
d.merge(n.children[i+1], out)
continue
}
if !d.unmarshal(ni, name) {
continue
}
if info, ok := sinfo.FieldsMap[name.String()]; ok {
if d.uniqueKeys {
if d.strict {
if doneFields[info.Id] {
d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s already set in type %s", ni.Line, name.String(), out.Type()))
d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s already set in type %s", ni.line+1, name.String(), out.Type()))
continue
}
doneFields[info.Id] = true
@@ -811,18 +720,18 @@ func (d *decoder) mappingStruct(n *Node, out reflect.Value) (good bool) {
if info.Inline == nil {
field = out.Field(info.Num)
} else {
field = d.fieldByIndex(n, out, info.Inline)
field = out.FieldByIndex(info.Inline)
}
d.unmarshal(n.Content[i+1], field)
d.unmarshal(n.children[i+1], field)
} else if sinfo.InlineMap != -1 {
if inlineMap.IsNil() {
inlineMap.Set(reflect.MakeMap(inlineMap.Type()))
}
value := reflect.New(elemType).Elem()
d.unmarshal(n.Content[i+1], value)
inlineMap.SetMapIndex(name, value)
} else if d.knownFields {
d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s not found in type %s", ni.Line, name.String(), out.Type()))
d.unmarshal(n.children[i+1], value)
d.setMapIndex(n.children[i+1], inlineMap, name, value)
} else if d.strict {
d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s not found in type %s", ni.line+1, name.String(), out.Type()))
}
}
return true
@@ -832,24 +741,26 @@ func failWantMap() {
failf("map merge requires map or sequence of maps as the value")
}
func (d *decoder) merge(n *Node, out reflect.Value) {
switch n.Kind {
case MappingNode:
func (d *decoder) merge(n *node, out reflect.Value) {
switch n.kind {
case mappingNode:
d.unmarshal(n, out)
case AliasNode:
if n.Alias != nil && n.Alias.Kind != MappingNode {
case aliasNode:
an, ok := d.doc.anchors[n.value]
if ok && an.kind != mappingNode {
failWantMap()
}
d.unmarshal(n, out)
case SequenceNode:
case sequenceNode:
// Step backwards as earlier nodes take precedence.
for i := len(n.Content) - 1; i >= 0; i-- {
ni := n.Content[i]
if ni.Kind == AliasNode {
if ni.Alias != nil && ni.Alias.Kind != MappingNode {
for i := len(n.children) - 1; i >= 0; i-- {
ni := n.children[i]
if ni.kind == aliasNode {
an, ok := d.doc.anchors[ni.value]
if ok && an.kind != mappingNode {
failWantMap()
}
} else if ni.Kind != MappingNode {
} else if ni.kind != mappingNode {
failWantMap()
}
d.unmarshal(ni, out)
@@ -859,6 +770,6 @@ func (d *decoder) merge(n *Node, out reflect.Value) {
}
}
func isMerge(n *Node) bool {
return n.Kind == ScalarNode && n.Value == "<<" && (n.Tag == "" || n.Tag == "!" || shortTag(n.Tag) == mergeTag)
func isMerge(n *node) bool {
return n.kind == scalarNode && n.value == "<<" && (n.implicit == true || n.tag == yaml_MERGE_TAG)
}

View File

@@ -1,25 +1,3 @@
//
// Copyright (c) 2011-2019 Canonical Ltd
// Copyright (c) 2006-2010 Kirill Simonov
//
// 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.
package yaml
import (
@@ -65,13 +43,8 @@ func put_break(emitter *yaml_emitter_t) bool {
default:
panic("unknown line break setting")
}
if emitter.column == 0 {
emitter.space_above = true
}
emitter.column = 0
emitter.line++
// [Go] Do this here and below and drop from everywhere else (see commented lines).
emitter.indention = true
return true
}
@@ -124,13 +97,8 @@ func write_break(emitter *yaml_emitter_t, s []byte, i *int) bool {
if !write(emitter, s, i) {
return false
}
if emitter.column == 0 {
emitter.space_above = true
}
emitter.column = 0
emitter.line++
// [Go] Do this here and above and drop from everywhere else (see commented lines).
emitter.indention = true
}
return true
}
@@ -260,22 +228,16 @@ func yaml_emitter_state_machine(emitter *yaml_emitter_t, event *yaml_event_t) bo
return yaml_emitter_emit_document_end(emitter, event)
case yaml_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE:
return yaml_emitter_emit_flow_sequence_item(emitter, event, true, false)
case yaml_EMIT_FLOW_SEQUENCE_TRAIL_ITEM_STATE:
return yaml_emitter_emit_flow_sequence_item(emitter, event, false, true)
return yaml_emitter_emit_flow_sequence_item(emitter, event, true)
case yaml_EMIT_FLOW_SEQUENCE_ITEM_STATE:
return yaml_emitter_emit_flow_sequence_item(emitter, event, false, false)
return yaml_emitter_emit_flow_sequence_item(emitter, event, false)
case yaml_EMIT_FLOW_MAPPING_FIRST_KEY_STATE:
return yaml_emitter_emit_flow_mapping_key(emitter, event, true, false)
case yaml_EMIT_FLOW_MAPPING_TRAIL_KEY_STATE:
return yaml_emitter_emit_flow_mapping_key(emitter, event, false, true)
return yaml_emitter_emit_flow_mapping_key(emitter, event, true)
case yaml_EMIT_FLOW_MAPPING_KEY_STATE:
return yaml_emitter_emit_flow_mapping_key(emitter, event, false, false)
return yaml_emitter_emit_flow_mapping_key(emitter, event, false)
case yaml_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE:
return yaml_emitter_emit_flow_mapping_value(emitter, event, true)
@@ -336,7 +298,6 @@ func yaml_emitter_emit_stream_start(emitter *yaml_emitter_t, event *yaml_event_t
emitter.column = 0
emitter.whitespace = true
emitter.indention = true
emitter.space_above = true
if emitter.encoding != yaml_UTF8_ENCODING {
if !yaml_emitter_write_bom(emitter) {
@@ -431,22 +392,13 @@ func yaml_emitter_emit_document_start(emitter *yaml_emitter_t, event *yaml_event
if !yaml_emitter_write_indicator(emitter, []byte("---"), true, false, false) {
return false
}
if emitter.canonical || true {
if emitter.canonical {
if !yaml_emitter_write_indent(emitter) {
return false
}
}
}
if len(emitter.head_comment) > 0 {
if !yaml_emitter_process_head_comment(emitter) {
return false
}
if !put_break(emitter) {
return false
}
}
emitter.state = yaml_EMIT_DOCUMENT_CONTENT_STATE
return true
}
@@ -473,20 +425,7 @@ func yaml_emitter_emit_document_start(emitter *yaml_emitter_t, event *yaml_event
// Expect the root node.
func yaml_emitter_emit_document_content(emitter *yaml_emitter_t, event *yaml_event_t) bool {
emitter.states = append(emitter.states, yaml_EMIT_DOCUMENT_END_STATE)
if !yaml_emitter_process_head_comment(emitter) {
return false
}
if !yaml_emitter_emit_node(emitter, event, true, false, false, false) {
return false
}
if !yaml_emitter_process_line_comment(emitter) {
return false
}
if !yaml_emitter_process_foot_comment(emitter) {
return false
}
return true
return yaml_emitter_emit_node(emitter, event, true, false, false, false)
}
// Expect DOCUMENT-END.
@@ -497,14 +436,6 @@ func yaml_emitter_emit_document_end(emitter *yaml_emitter_t, event *yaml_event_t
if !yaml_emitter_write_indent(emitter) {
return false
}
if len(emitter.foot_comment) > 0 {
if !put_break(emitter) {
return false
}
if !yaml_emitter_process_foot_comment(emitter) {
return false
}
}
if !event.implicit {
// [Go] Allocate the slice elsewhere.
if !yaml_emitter_write_indicator(emitter, []byte("..."), true, false, false) {
@@ -523,7 +454,7 @@ func yaml_emitter_emit_document_end(emitter *yaml_emitter_t, event *yaml_event_t
}
// Expect a flow item node.
func yaml_emitter_emit_flow_sequence_item(emitter *yaml_emitter_t, event *yaml_event_t, first, trail bool) bool {
func yaml_emitter_emit_flow_sequence_item(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool {
if first {
if !yaml_emitter_write_indicator(emitter, []byte{'['}, true, true, false) {
return false
@@ -549,62 +480,29 @@ func yaml_emitter_emit_flow_sequence_item(emitter *yaml_emitter_t, event *yaml_e
if !yaml_emitter_write_indicator(emitter, []byte{']'}, false, false, false) {
return false
}
if !yaml_emitter_process_line_comment(emitter) {
return false
}
if !yaml_emitter_process_foot_comment(emitter) {
return false
}
emitter.state = emitter.states[len(emitter.states)-1]
emitter.states = emitter.states[:len(emitter.states)-1]
return true
}
if !first && !trail {
if !first {
if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) {
return false
}
}
if !yaml_emitter_process_head_comment(emitter) {
return false
}
if emitter.column == 0 {
if !yaml_emitter_write_indent(emitter) {
return false
}
}
if emitter.canonical || emitter.column > emitter.best_width {
if !yaml_emitter_write_indent(emitter) {
return false
}
}
if len(emitter.line_comment) > 0 || len(emitter.foot_comment) > 0 {
emitter.states = append(emitter.states, yaml_EMIT_FLOW_SEQUENCE_TRAIL_ITEM_STATE)
} else {
emitter.states = append(emitter.states, yaml_EMIT_FLOW_SEQUENCE_ITEM_STATE)
}
if !yaml_emitter_emit_node(emitter, event, false, true, false, false) {
return false
}
if len(emitter.line_comment) > 0 || len(emitter.foot_comment) > 0 {
if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) {
return false
}
}
if !yaml_emitter_process_line_comment(emitter) {
return false
}
if !yaml_emitter_process_foot_comment(emitter) {
return false
}
return true
emitter.states = append(emitter.states, yaml_EMIT_FLOW_SEQUENCE_ITEM_STATE)
return yaml_emitter_emit_node(emitter, event, false, true, false, false)
}
// Expect a flow key node.
func yaml_emitter_emit_flow_mapping_key(emitter *yaml_emitter_t, event *yaml_event_t, first, trail bool) bool {
func yaml_emitter_emit_flow_mapping_key(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool {
if first {
if !yaml_emitter_write_indicator(emitter, []byte{'{'}, true, true, false) {
return false
@@ -630,32 +528,16 @@ func yaml_emitter_emit_flow_mapping_key(emitter *yaml_emitter_t, event *yaml_eve
if !yaml_emitter_write_indicator(emitter, []byte{'}'}, false, false, false) {
return false
}
if !yaml_emitter_process_line_comment(emitter) {
return false
}
if !yaml_emitter_process_foot_comment(emitter) {
return false
}
emitter.state = emitter.states[len(emitter.states)-1]
emitter.states = emitter.states[:len(emitter.states)-1]
return true
}
if !first && !trail {
if !first {
if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) {
return false
}
}
if !yaml_emitter_process_head_comment(emitter) {
return false
}
if emitter.column == 0 {
if !yaml_emitter_write_indent(emitter) {
return false
}
}
if emitter.canonical || emitter.column > emitter.best_width {
if !yaml_emitter_write_indent(emitter) {
return false
@@ -689,26 +571,8 @@ func yaml_emitter_emit_flow_mapping_value(emitter *yaml_emitter_t, event *yaml_e
return false
}
}
if len(emitter.line_comment) > 0 || len(emitter.foot_comment) > 0 {
emitter.states = append(emitter.states, yaml_EMIT_FLOW_MAPPING_TRAIL_KEY_STATE)
} else {
emitter.states = append(emitter.states, yaml_EMIT_FLOW_MAPPING_KEY_STATE)
}
if !yaml_emitter_emit_node(emitter, event, false, false, true, false) {
return false
}
if len(emitter.line_comment) > 0 || len(emitter.foot_comment) > 0 {
if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) {
return false
}
}
if !yaml_emitter_process_line_comment(emitter) {
return false
}
if !yaml_emitter_process_foot_comment(emitter) {
return false
}
return true
emitter.states = append(emitter.states, yaml_EMIT_FLOW_MAPPING_KEY_STATE)
return yaml_emitter_emit_node(emitter, event, false, false, true, false)
}
// Expect a block item node.
@@ -725,9 +589,6 @@ func yaml_emitter_emit_block_sequence_item(emitter *yaml_emitter_t, event *yaml_
emitter.states = emitter.states[:len(emitter.states)-1]
return true
}
if !yaml_emitter_process_head_comment(emitter) {
return false
}
if !yaml_emitter_write_indent(emitter) {
return false
}
@@ -735,16 +596,7 @@ func yaml_emitter_emit_block_sequence_item(emitter *yaml_emitter_t, event *yaml_
return false
}
emitter.states = append(emitter.states, yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE)
if !yaml_emitter_emit_node(emitter, event, false, true, false, false) {
return false
}
if !yaml_emitter_process_line_comment(emitter) {
return false
}
if !yaml_emitter_process_foot_comment(emitter) {
return false
}
return true
return yaml_emitter_emit_node(emitter, event, false, true, false, false)
}
// Expect a block key node.
@@ -761,14 +613,9 @@ func yaml_emitter_emit_block_mapping_key(emitter *yaml_emitter_t, event *yaml_ev
emitter.states = emitter.states[:len(emitter.states)-1]
return true
}
if !yaml_emitter_process_head_comment(emitter) {
if !yaml_emitter_write_indent(emitter) {
return false
}
if !first || emitter.states[len(emitter.states)-1] != yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE {
if !yaml_emitter_write_indent(emitter) {
return false
}
}
if yaml_emitter_check_simple_key(emitter) {
emitter.states = append(emitter.states, yaml_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE)
return yaml_emitter_emit_node(emitter, event, false, false, true, true)
@@ -795,16 +642,7 @@ func yaml_emitter_emit_block_mapping_value(emitter *yaml_emitter_t, event *yaml_
}
}
emitter.states = append(emitter.states, yaml_EMIT_BLOCK_MAPPING_KEY_STATE)
if !yaml_emitter_emit_node(emitter, event, false, false, true, false) {
return false
}
if !yaml_emitter_process_line_comment(emitter) {
return false
}
if !yaml_emitter_process_foot_comment(emitter) {
return false
}
return true
return yaml_emitter_emit_node(emitter, event, false, false, true, false)
}
// Expect a node.
@@ -1070,68 +908,6 @@ func yaml_emitter_process_scalar(emitter *yaml_emitter_t) bool {
panic("unknown scalar style")
}
// Write a head comment.
func yaml_emitter_process_head_comment(emitter *yaml_emitter_t) bool {
if len(emitter.head_comment) == 0 {
return true
}
space_above := emitter.space_above
if !emitter.indention {
if !put_break(emitter) {
return false
}
}
if !space_above &&
emitter.state != yaml_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE &&
emitter.state != yaml_EMIT_FLOW_MAPPING_FIRST_KEY_STATE &&
emitter.state != yaml_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE &&
emitter.state != yaml_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE {
if !put_break(emitter) {
return false
}
}
if !yaml_emitter_write_indent(emitter) {
return false
}
if !yaml_emitter_write_comment(emitter, emitter.head_comment) {
return false
}
emitter.head_comment = emitter.head_comment[:0]
return true
}
// Write an line comment.
func yaml_emitter_process_line_comment(emitter *yaml_emitter_t) bool {
if len(emitter.line_comment) == 0 {
return true
}
if !emitter.whitespace {
if !put(emitter, ' ') {
return false
}
}
if !yaml_emitter_write_comment(emitter, emitter.line_comment) {
return false
}
emitter.line_comment = emitter.line_comment[:0]
return true
}
// Write a foot comment.
func yaml_emitter_process_foot_comment(emitter *yaml_emitter_t) bool {
if len(emitter.foot_comment) == 0 {
return true
}
if !yaml_emitter_write_indent(emitter) {
return false
}
if !yaml_emitter_write_comment(emitter, emitter.foot_comment) {
return false
}
emitter.foot_comment = emitter.foot_comment[:0]
return true
}
// Check if a %YAML directive is valid.
func yaml_emitter_analyze_version_directive(emitter *yaml_emitter_t, version_directive *yaml_version_directive_t) bool {
if version_directive.major != 1 || version_directive.minor != 1 {
@@ -1361,16 +1137,6 @@ func yaml_emitter_analyze_event(emitter *yaml_emitter_t, event *yaml_event_t) bo
emitter.tag_data.suffix = nil
emitter.scalar_data.value = nil
if len(event.head_comment) > 0 {
emitter.head_comment = event.head_comment
}
if len(event.line_comment) > 0 {
emitter.line_comment = event.line_comment
}
if len(event.foot_comment) > 0 {
emitter.foot_comment = event.foot_comment
}
switch event.typ {
case yaml_ALIAS_EVENT:
if !yaml_emitter_analyze_anchor(emitter, event.anchor, true) {
@@ -1448,8 +1214,7 @@ func yaml_emitter_write_indent(emitter *yaml_emitter_t) bool {
}
}
emitter.whitespace = true
//emitter.indention = true
emitter.space_above = false
emitter.indention = true
return true
}
@@ -1576,7 +1341,7 @@ func yaml_emitter_write_plain_scalar(emitter *yaml_emitter_t, value []byte, allo
if !write_break(emitter, value, &i) {
return false
}
//emitter.indention = true
emitter.indention = true
breaks = true
} else {
if breaks {
@@ -1632,7 +1397,7 @@ func yaml_emitter_write_single_quoted_scalar(emitter *yaml_emitter_t, value []by
if !write_break(emitter, value, &i) {
return false
}
//emitter.indention = true
emitter.indention = true
breaks = true
} else {
if breaks {
@@ -1834,7 +1599,7 @@ func yaml_emitter_write_literal_scalar(emitter *yaml_emitter_t, value []byte) bo
if !put_break(emitter) {
return false
}
//emitter.indention = true
emitter.indention = true
emitter.whitespace = true
breaks := true
for i := 0; i < len(value); {
@@ -1842,7 +1607,7 @@ func yaml_emitter_write_literal_scalar(emitter *yaml_emitter_t, value []byte) bo
if !write_break(emitter, value, &i) {
return false
}
//emitter.indention = true
emitter.indention = true
breaks = true
} else {
if breaks {
@@ -1872,7 +1637,7 @@ func yaml_emitter_write_folded_scalar(emitter *yaml_emitter_t, value []byte) boo
if !put_break(emitter) {
return false
}
//emitter.indention = true
emitter.indention = true
emitter.whitespace = true
breaks := true
@@ -1893,7 +1658,7 @@ func yaml_emitter_write_folded_scalar(emitter *yaml_emitter_t, value []byte) boo
if !write_break(emitter, value, &i) {
return false
}
//emitter.indention = true
emitter.indention = true
breaks = true
} else {
if breaks {
@@ -1918,40 +1683,3 @@ func yaml_emitter_write_folded_scalar(emitter *yaml_emitter_t, value []byte) boo
}
return true
}
func yaml_emitter_write_comment(emitter *yaml_emitter_t, comment []byte) bool {
breaks := false
pound := false
for i := 0; i < len(comment); {
if is_break(comment, i) {
if !write_break(emitter, comment, &i) {
return false
}
//emitter.indention = true
breaks = true
pound = false
} else {
if breaks && !yaml_emitter_write_indent(emitter) {
return false
}
if !pound {
if comment[i] != '#' && (!put(emitter, '#') || !put(emitter, ' ')) {
return false
}
pound = true
}
if !write(emitter, comment, &i) {
return false
}
emitter.indention = false
breaks = false
}
}
if !breaks && !put_break(emitter) {
return false
}
emitter.whitespace = true
//emitter.indention = true
return true
}

View File

@@ -1,18 +1,3 @@
//
// Copyright (c) 2011-2019 Canonical Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package yaml
import (
@@ -29,11 +14,12 @@ import (
)
type encoder struct {
emitter yaml_emitter_t
event yaml_event_t
out []byte
flow bool
indent int
emitter yaml_emitter_t
event yaml_event_t
out []byte
flow bool
// doneInit holds whether the initial stream_start_event has been
// emitted.
doneInit bool
}
@@ -57,10 +43,6 @@ func (e *encoder) init() {
if e.doneInit {
return
}
if e.indent == 0 {
e.indent = 4
}
e.emitter.best_indent = e.indent
yaml_stream_start_event_initialize(&e.event, yaml_UTF8_ENCODING)
e.emit()
e.doneInit = true
@@ -93,43 +75,27 @@ func (e *encoder) must(ok bool) {
func (e *encoder) marshalDoc(tag string, in reflect.Value) {
e.init()
var node *Node
if in.IsValid() {
node, _ = in.Interface().(*Node)
}
if node != nil && node.Kind == DocumentNode {
e.nodev(in)
} else {
yaml_document_start_event_initialize(&e.event, nil, nil, true)
e.emit()
e.marshal(tag, in)
yaml_document_end_event_initialize(&e.event, true)
e.emit()
}
yaml_document_start_event_initialize(&e.event, nil, nil, true)
e.emit()
e.marshal(tag, in)
yaml_document_end_event_initialize(&e.event, true)
e.emit()
}
func (e *encoder) marshal(tag string, in reflect.Value) {
tag = shortTag(tag)
if !in.IsValid() || in.Kind() == reflect.Ptr && in.IsNil() {
e.nilv()
return
}
iface := in.Interface()
switch value := iface.(type) {
case *Node:
e.nodev(in)
return
case time.Time:
e.timev(tag, in)
return
case *time.Time:
e.timev(tag, in.Elem())
return
case time.Duration:
e.stringv(tag, reflect.ValueOf(value.String()))
return
switch m := iface.(type) {
case time.Time, *time.Time:
// Although time.Time implements TextMarshaler,
// we don't want to treat it as a string for YAML
// purposes because YAML has special support for
// timestamps.
case Marshaler:
v, err := value.MarshalYAML()
v, err := m.MarshalYAML()
if err != nil {
fail(err)
}
@@ -137,10 +103,9 @@ func (e *encoder) marshal(tag string, in reflect.Value) {
e.nilv()
return
}
e.marshal(tag, reflect.ValueOf(v))
return
in = reflect.ValueOf(v)
case encoding.TextMarshaler:
text, err := value.MarshalText()
text, err := m.MarshalText()
if err != nil {
fail(err)
}
@@ -155,15 +120,31 @@ func (e *encoder) marshal(tag string, in reflect.Value) {
case reflect.Map:
e.mapv(tag, in)
case reflect.Ptr:
e.marshal(tag, in.Elem())
if in.Type() == ptrTimeType {
e.timev(tag, in.Elem())
} else {
e.marshal(tag, in.Elem())
}
case reflect.Struct:
e.structv(tag, in)
if in.Type() == timeType {
e.timev(tag, in)
} else {
e.structv(tag, in)
}
case reflect.Slice, reflect.Array:
e.slicev(tag, in)
if in.Type().Elem() == mapItemType {
e.itemsv(tag, in)
} else {
e.slicev(tag, in)
}
case reflect.String:
e.stringv(tag, in)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
e.intv(tag, in)
if in.Type() == durationType {
e.stringv(tag, reflect.ValueOf(iface.(time.Duration).String()))
} else {
e.intv(tag, in)
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
e.uintv(tag, in)
case reflect.Float32, reflect.Float64:
@@ -186,21 +167,14 @@ func (e *encoder) mapv(tag string, in reflect.Value) {
})
}
func (e *encoder) fieldByIndex(v reflect.Value, index []int) (field reflect.Value) {
for _, num := range index {
for {
if v.Kind() == reflect.Ptr {
if v.IsNil() {
return reflect.Value{}
}
v = v.Elem()
continue
}
break
func (e *encoder) itemsv(tag string, in reflect.Value) {
e.mappingv(tag, func() {
slice := in.Convert(reflect.TypeOf([]MapItem{})).Interface().([]MapItem)
for _, item := range slice {
e.marshal("", reflect.ValueOf(item.Key))
e.marshal("", reflect.ValueOf(item.Value))
}
v = v.Field(num)
}
return v
})
}
func (e *encoder) structv(tag string, in reflect.Value) {
@@ -214,10 +188,7 @@ func (e *encoder) structv(tag string, in reflect.Value) {
if info.Inline == nil {
value = in.Field(info.Num)
} else {
value = e.fieldByIndex(in, info.Inline)
if !value.IsValid() {
continue
}
value = in.FieldByIndex(info.Inline)
}
if info.OmitEmpty && isZero(value) {
continue
@@ -234,7 +205,7 @@ func (e *encoder) structv(tag string, in reflect.Value) {
sort.Sort(keys)
for _, k := range keys {
if _, found := sinfo.FieldsMap[k.String()]; found {
panic(fmt.Sprintf("cannot have key %q in inlined map: conflicts with struct field", k.String()))
panic(fmt.Sprintf("Can't have key %q in inlined map; conflicts with struct field", k.String()))
}
e.marshal("", k)
e.flow = false
@@ -304,7 +275,7 @@ func (e *encoder) stringv(tag string, in reflect.Value) {
canUsePlain := true
switch {
case !utf8.ValidString(s):
if tag == binaryTag {
if tag == yaml_BINARY_TAG {
failf("explicitly tagged !!binary data must be base64-encoded")
}
if tag != "" {
@@ -312,14 +283,14 @@ func (e *encoder) stringv(tag string, in reflect.Value) {
}
// It can't be encoded directly as YAML so use a binary tag
// and encode it as base64.
tag = binaryTag
tag = yaml_BINARY_TAG
s = encodeBase64(s)
case tag == "":
// Check to see if it would resolve to a specific
// tag when encoded unquoted. If it doesn't,
// there's no need to quote it.
rtag, _ := resolve("", s)
canUsePlain = rtag == strTag && !isBase60Float(s)
canUsePlain = rtag == yaml_STR_TAG && !isBase60Float(s)
}
// Note: it's possible for user code to emit invalid YAML
// if they explicitly specify a tag and a string containing
@@ -332,7 +303,7 @@ func (e *encoder) stringv(tag string, in reflect.Value) {
default:
style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
}
e.emitScalar(s, "", tag, style, nil, nil, nil)
e.emitScalar(s, "", tag, style)
}
func (e *encoder) boolv(tag string, in reflect.Value) {
@@ -342,23 +313,23 @@ func (e *encoder) boolv(tag string, in reflect.Value) {
} else {
s = "false"
}
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil)
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
}
func (e *encoder) intv(tag string, in reflect.Value) {
s := strconv.FormatInt(in.Int(), 10)
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil)
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
}
func (e *encoder) uintv(tag string, in reflect.Value) {
s := strconv.FormatUint(in.Uint(), 10)
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil)
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
}
func (e *encoder) timev(tag string, in reflect.Value) {
t := in.Interface().(time.Time)
s := t.Format(time.RFC3339Nano)
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil)
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
}
func (e *encoder) floatv(tag string, in reflect.Value) {
@@ -377,148 +348,15 @@ func (e *encoder) floatv(tag string, in reflect.Value) {
case "NaN":
s = ".nan"
}
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil)
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
}
func (e *encoder) nilv() {
e.emitScalar("null", "", "", yaml_PLAIN_SCALAR_STYLE, nil, nil, nil)
e.emitScalar("null", "", "", yaml_PLAIN_SCALAR_STYLE)
}
func (e *encoder) emitScalar(value, anchor, tag string, style yaml_scalar_style_t, head, line, foot []byte) {
// TODO Kill this function. Replace all initialize calls by their underlining Go literals.
func (e *encoder) emitScalar(value, anchor, tag string, style yaml_scalar_style_t) {
implicit := tag == ""
if !implicit {
tag = longTag(tag)
}
e.must(yaml_scalar_event_initialize(&e.event, []byte(anchor), []byte(tag), []byte(value), implicit, implicit, style))
e.event.head_comment = head
e.event.line_comment = line
e.event.foot_comment = foot
e.emit()
}
func (e *encoder) nodev(in reflect.Value) {
e.node(in.Interface().(*Node))
}
func (e *encoder) node(node *Node) {
// If the tag was not explicitly requested, and dropping it won't change the
// implicit tag of the value, don't include it in the presentation.
var tag = node.Tag
var stag = shortTag(tag)
var rtag string
var forceQuoting bool
if tag != "" && node.Style&TaggedStyle == 0 {
if node.Kind == ScalarNode {
if stag == strTag && node.Style&(SingleQuotedStyle|DoubleQuotedStyle|LiteralStyle|FoldedStyle) != 0 {
tag = ""
} else {
rtag, _ = resolve("", node.Value)
if rtag == stag {
tag = ""
} else if stag == strTag {
tag = ""
forceQuoting = true
}
}
} else {
switch node.Kind {
case MappingNode:
rtag = mapTag
case SequenceNode:
rtag = seqTag
}
if rtag == stag {
tag = ""
}
}
}
switch node.Kind {
case DocumentNode:
yaml_document_start_event_initialize(&e.event, nil, nil, true)
e.event.head_comment = []byte(node.HeadComment)
e.emit()
for _, node := range node.Content {
e.node(node)
}
yaml_document_end_event_initialize(&e.event, true)
e.event.foot_comment = []byte(node.FootComment)
e.emit()
case SequenceNode:
style := yaml_BLOCK_SEQUENCE_STYLE
if node.Style&FlowStyle != 0 {
style = yaml_FLOW_SEQUENCE_STYLE
}
e.must(yaml_sequence_start_event_initialize(&e.event, []byte(node.Anchor), []byte(tag), tag == "", style))
e.event.head_comment = []byte(node.HeadComment)
e.emit()
for _, node := range node.Content {
e.node(node)
}
e.must(yaml_sequence_end_event_initialize(&e.event))
e.event.line_comment = []byte(node.LineComment)
e.event.foot_comment = []byte(node.FootComment)
e.emit()
case MappingNode:
style := yaml_BLOCK_MAPPING_STYLE
if node.Style&FlowStyle != 0 {
style = yaml_FLOW_MAPPING_STYLE
}
yaml_mapping_start_event_initialize(&e.event, []byte(node.Anchor), []byte(tag), tag == "", style)
e.event.head_comment = []byte(node.HeadComment)
e.emit()
for i := 0; i+1 < len(node.Content); i += 2 {
e.node(node.Content[i])
e.node(node.Content[i+1])
}
yaml_mapping_end_event_initialize(&e.event)
e.event.line_comment = []byte(node.LineComment)
e.event.foot_comment = []byte(node.FootComment)
e.emit()
case AliasNode:
yaml_alias_event_initialize(&e.event, []byte(node.Value))
e.event.head_comment = []byte(node.HeadComment)
e.event.line_comment = []byte(node.LineComment)
e.event.foot_comment = []byte(node.FootComment)
e.emit()
case ScalarNode:
value := node.Value
if !utf8.ValidString(value) {
if tag == binaryTag {
failf("explicitly tagged !!binary data must be base64-encoded")
}
if tag != "" {
failf("cannot marshal invalid UTF-8 data as %s", shortTag(tag))
}
// It can't be encoded directly as YAML so use a binary tag
// and encode it as base64.
tag = binaryTag
value = encodeBase64(value)
}
style := yaml_PLAIN_SCALAR_STYLE
switch {
case node.Style&DoubleQuotedStyle != 0:
style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
case node.Style&SingleQuotedStyle != 0:
style = yaml_SINGLE_QUOTED_SCALAR_STYLE
case node.Style&LiteralStyle != 0:
style = yaml_LITERAL_SCALAR_STYLE
case node.Style&FoldedStyle != 0:
style = yaml_FOLDED_SCALAR_STYLE
case strings.Contains(value, "\n"):
style = yaml_LITERAL_SCALAR_STYLE
case forceQuoting:
style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
}
e.emitScalar(value, node.Anchor, tag, style, []byte(node.HeadComment), []byte(node.LineComment), []byte(node.FootComment))
}
}

View File

@@ -1,4 +1,4 @@
module "gopkg.in/yaml.v3"
module "gopkg.in/yaml.v2"
require (
"gopkg.in/check.v1" v0.0.0-20161208181325-20d25e280405

View File

@@ -1,25 +1,3 @@
//
// Copyright (c) 2011-2019 Canonical Ltd
// Copyright (c) 2006-2010 Kirill Simonov
//
// 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.
package yaml
import (
@@ -67,42 +45,11 @@ import (
// Peek the next token in the token queue.
func peek_token(parser *yaml_parser_t) *yaml_token_t {
if parser.token_available || yaml_parser_fetch_more_tokens(parser) {
token := &parser.tokens[parser.tokens_head]
yaml_parser_unfold_comments(parser, token)
return token
return &parser.tokens[parser.tokens_head]
}
return nil
}
// yaml_parser_unfold_comments walks through the comments queue and joins all
// comments behind the position of the provided token into the respective
// top-level comment slices in the parser.
func yaml_parser_unfold_comments(parser *yaml_parser_t, token *yaml_token_t) {
for parser.comments_head < len(parser.comments) && token.start_mark.index >= parser.comments[parser.comments_head].after.index {
comment := &parser.comments[parser.comments_head]
if len(comment.head) > 0 {
if len(parser.head_comment) > 0 {
parser.head_comment = append(parser.head_comment, '\n')
}
parser.head_comment = append(parser.head_comment, comment.head...)
}
if len(comment.foot) > 0 {
if len(parser.foot_comment) > 0 {
parser.foot_comment = append(parser.foot_comment, '\n')
}
parser.foot_comment = append(parser.foot_comment, comment.foot...)
}
if len(comment.line) > 0 {
if len(parser.line_comment) > 0 {
parser.line_comment = append(parser.line_comment, '\n')
}
parser.line_comment = append(parser.line_comment, comment.line...)
}
*comment = yaml_comment_t{}
parser.comments_head++
}
}
// Remove the next token from the queue (must be called after peek_token).
func skip_token(parser *yaml_parser_t) {
parser.token_available = false
@@ -277,32 +224,10 @@ func yaml_parser_parse_document_start(parser *yaml_parser_t, event *yaml_event_t
parser.states = append(parser.states, yaml_PARSE_DOCUMENT_END_STATE)
parser.state = yaml_PARSE_BLOCK_NODE_STATE
var head_comment []byte
if len(parser.head_comment) > 0 {
// [Go] Scan the header comment backwards, and if an empty line is found, break
// the header so the part before the last empty line goes into the
// document header, while the bottom of it goes into a follow up event.
for i := len(parser.head_comment) - 1; i > 0; i-- {
if parser.head_comment[i] == '\n' {
if i == len(parser.head_comment)-1 {
head_comment = parser.head_comment[:i]
parser.head_comment = parser.head_comment[i+1:]
break
} else if parser.head_comment[i-1] == '\n' {
head_comment = parser.head_comment[:i-1]
parser.head_comment = parser.head_comment[i+1:]
break
}
}
}
}
*event = yaml_event_t{
typ: yaml_DOCUMENT_START_EVENT,
start_mark: token.start_mark,
end_mark: token.end_mark,
head_comment: head_comment,
}
} else if token.typ != yaml_STREAM_END_TOKEN {
@@ -401,22 +326,10 @@ func yaml_parser_parse_document_end(parser *yaml_parser_t, event *yaml_event_t)
start_mark: start_mark,
end_mark: end_mark,
implicit: implicit,
foot_comment: parser.head_comment,
}
parser.head_comment = nil
return true
}
func yaml_parser_set_event_comments(parser *yaml_parser_t, event *yaml_event_t) {
event.head_comment = parser.head_comment
event.line_comment = parser.line_comment
event.foot_comment = parser.foot_comment
parser.head_comment = nil
parser.line_comment = nil
parser.foot_comment = nil
}
// Parse the productions:
// block_node_or_indentless_sequence ::=
// ALIAS
@@ -460,7 +373,6 @@ func yaml_parser_parse_node(parser *yaml_parser_t, event *yaml_event_t, block, i
end_mark: token.end_mark,
anchor: token.value,
}
yaml_parser_set_event_comments(parser, event)
skip_token(parser)
return true
}
@@ -574,7 +486,6 @@ func yaml_parser_parse_node(parser *yaml_parser_t, event *yaml_event_t, block, i
quoted_implicit: quoted_implicit,
style: yaml_style_t(token.style),
}
yaml_parser_set_event_comments(parser, event)
skip_token(parser)
return true
}
@@ -591,7 +502,6 @@ func yaml_parser_parse_node(parser *yaml_parser_t, event *yaml_event_t, block, i
implicit: implicit,
style: yaml_style_t(yaml_FLOW_SEQUENCE_STYLE),
}
yaml_parser_set_event_comments(parser, event)
return true
}
if token.typ == yaml_FLOW_MAPPING_START_TOKEN {
@@ -606,7 +516,6 @@ func yaml_parser_parse_node(parser *yaml_parser_t, event *yaml_event_t, block, i
implicit: implicit,
style: yaml_style_t(yaml_FLOW_MAPPING_STYLE),
}
yaml_parser_set_event_comments(parser, event)
return true
}
if block && token.typ == yaml_BLOCK_SEQUENCE_START_TOKEN {
@@ -911,7 +820,6 @@ func yaml_parser_parse_flow_sequence_entry(parser *yaml_parser_t, event *yaml_ev
start_mark: token.start_mark,
end_mark: token.end_mark,
}
yaml_parser_set_event_comments(parser, event)
skip_token(parser)
return true
@@ -1051,7 +959,6 @@ func yaml_parser_parse_flow_mapping_key(parser *yaml_parser_t, event *yaml_event
start_mark: token.start_mark,
end_mark: token.end_mark,
}
yaml_parser_set_event_comments(parser, event)
skip_token(parser)
return true
}

View File

@@ -1,25 +1,3 @@
//
// Copyright (c) 2011-2019 Canonical Ltd
// Copyright (c) 2006-2010 Kirill Simonov
//
// 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.
package yaml
import (
@@ -117,7 +95,7 @@ func yaml_parser_update_buffer(parser *yaml_parser_t, length int) bool {
// [Go] This function was changed to guarantee the requested length size at EOF.
// The fact we need to do this is pretty awful, but the description above implies
// for that to be the case, and there are tests
// for that to be the case, and there are tests
// If the EOF flag is set and the raw buffer is empty, do nothing.
if parser.eof && parser.raw_buffer_pos == len(parser.raw_buffer) {

View File

@@ -1,18 +1,3 @@
//
// Copyright (c) 2011-2019 Canonical Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package yaml
import (
@@ -49,14 +34,18 @@ func init() {
tag string
l []string
}{
{true, boolTag, []string{"true", "True", "TRUE"}},
{false, boolTag, []string{"false", "False", "FALSE"}},
{nil, nullTag, []string{"", "~", "null", "Null", "NULL"}},
{math.NaN(), floatTag, []string{".nan", ".NaN", ".NAN"}},
{math.Inf(+1), floatTag, []string{".inf", ".Inf", ".INF"}},
{math.Inf(+1), floatTag, []string{"+.inf", "+.Inf", "+.INF"}},
{math.Inf(-1), floatTag, []string{"-.inf", "-.Inf", "-.INF"}},
{"<<", mergeTag, []string{"<<"}},
{true, yaml_BOOL_TAG, []string{"y", "Y", "yes", "Yes", "YES"}},
{true, yaml_BOOL_TAG, []string{"true", "True", "TRUE"}},
{true, yaml_BOOL_TAG, []string{"on", "On", "ON"}},
{false, yaml_BOOL_TAG, []string{"n", "N", "no", "No", "NO"}},
{false, yaml_BOOL_TAG, []string{"false", "False", "FALSE"}},
{false, yaml_BOOL_TAG, []string{"off", "Off", "OFF"}},
{nil, yaml_NULL_TAG, []string{"", "~", "null", "Null", "NULL"}},
{math.NaN(), yaml_FLOAT_TAG, []string{".nan", ".NaN", ".NAN"}},
{math.Inf(+1), yaml_FLOAT_TAG, []string{".inf", ".Inf", ".INF"}},
{math.Inf(+1), yaml_FLOAT_TAG, []string{"+.inf", "+.Inf", "+.INF"}},
{math.Inf(-1), yaml_FLOAT_TAG, []string{"-.inf", "-.Inf", "-.INF"}},
{"<<", yaml_MERGE_TAG, []string{"<<"}},
}
m := resolveMap
@@ -67,37 +56,11 @@ func init() {
}
}
const (
nullTag = "!!null"
boolTag = "!!bool"
strTag = "!!str"
intTag = "!!int"
floatTag = "!!float"
timestampTag = "!!timestamp"
seqTag = "!!seq"
mapTag = "!!map"
binaryTag = "!!binary"
mergeTag = "!!merge"
)
var longTags = make(map[string]string)
var shortTags = make(map[string]string)
func init() {
for _, stag := range []string{nullTag, boolTag, strTag, intTag, floatTag, timestampTag, seqTag, mapTag, binaryTag, mergeTag} {
ltag := longTag(stag)
longTags[stag] = ltag
shortTags[ltag] = stag
}
}
const longTagPrefix = "tag:yaml.org,2002:"
func shortTag(tag string) string {
// TODO This can easily be made faster and produce less garbage.
if strings.HasPrefix(tag, longTagPrefix) {
if stag, ok := shortTags[tag]; ok {
return stag
}
return "!!" + tag[len(longTagPrefix):]
}
return tag
@@ -105,9 +68,6 @@ func shortTag(tag string) string {
func longTag(tag string) string {
if strings.HasPrefix(tag, "!!") {
if ltag, ok := longTags[tag]; ok {
return ltag
}
return longTagPrefix + tag[2:]
}
return tag
@@ -115,33 +75,32 @@ func longTag(tag string) string {
func resolvableTag(tag string) bool {
switch tag {
case "", strTag, boolTag, intTag, floatTag, nullTag, timestampTag:
case "", yaml_STR_TAG, yaml_BOOL_TAG, yaml_INT_TAG, yaml_FLOAT_TAG, yaml_NULL_TAG, yaml_TIMESTAMP_TAG:
return true
}
return false
}
var yamlStyleFloat = regexp.MustCompile(`^[-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?$`)
var yamlStyleFloat = regexp.MustCompile(`^[-+]?[0-9]*\.?[0-9]+([eE][-+][0-9]+)?$`)
func resolve(tag string, in string) (rtag string, out interface{}) {
tag = shortTag(tag)
if !resolvableTag(tag) {
return tag, in
}
defer func() {
switch tag {
case "", rtag, strTag, binaryTag:
case "", rtag, yaml_STR_TAG, yaml_BINARY_TAG:
return
case floatTag:
if rtag == intTag {
case yaml_FLOAT_TAG:
if rtag == yaml_INT_TAG {
switch v := out.(type) {
case int64:
rtag = floatTag
rtag = yaml_FLOAT_TAG
out = float64(v)
return
case int:
rtag = floatTag
rtag = yaml_FLOAT_TAG
out = float64(v)
return
}
@@ -156,7 +115,7 @@ func resolve(tag string, in string) (rtag string, out interface{}) {
if in != "" {
hint = resolveTable[in[0]]
}
if hint != 0 && tag != strTag && tag != binaryTag {
if hint != 0 && tag != yaml_STR_TAG && tag != yaml_BINARY_TAG {
// Handle things we can lookup in a map.
if item, ok := resolveMap[in]; ok {
return item.tag, item.value
@@ -174,17 +133,17 @@ func resolve(tag string, in string) (rtag string, out interface{}) {
// Not in the map, so maybe a normal float.
floatv, err := strconv.ParseFloat(in, 64)
if err == nil {
return floatTag, floatv
return yaml_FLOAT_TAG, floatv
}
case 'D', 'S':
// Int, float, or timestamp.
// Only try values as a timestamp if the value is unquoted or there's an explicit
// !!timestamp tag.
if tag == "" || tag == timestampTag {
if tag == "" || tag == yaml_TIMESTAMP_TAG {
t, ok := parseTimestamp(in)
if ok {
return timestampTag, t
return yaml_TIMESTAMP_TAG, t
}
}
@@ -192,76 +151,49 @@ func resolve(tag string, in string) (rtag string, out interface{}) {
intv, err := strconv.ParseInt(plain, 0, 64)
if err == nil {
if intv == int64(int(intv)) {
return intTag, int(intv)
return yaml_INT_TAG, int(intv)
} else {
return intTag, intv
return yaml_INT_TAG, intv
}
}
uintv, err := strconv.ParseUint(plain, 0, 64)
if err == nil {
return intTag, uintv
return yaml_INT_TAG, uintv
}
if yamlStyleFloat.MatchString(plain) {
floatv, err := strconv.ParseFloat(plain, 64)
if err == nil {
return floatTag, floatv
return yaml_FLOAT_TAG, floatv
}
}
if strings.HasPrefix(plain, "0b") {
intv, err := strconv.ParseInt(plain[2:], 2, 64)
if err == nil {
if intv == int64(int(intv)) {
return intTag, int(intv)
return yaml_INT_TAG, int(intv)
} else {
return intTag, intv
return yaml_INT_TAG, intv
}
}
uintv, err := strconv.ParseUint(plain[2:], 2, 64)
if err == nil {
return intTag, uintv
return yaml_INT_TAG, uintv
}
} else if strings.HasPrefix(plain, "-0b") {
intv, err := strconv.ParseInt("-"+plain[3:], 2, 64)
intv, err := strconv.ParseInt("-" + plain[3:], 2, 64)
if err == nil {
if true || intv == int64(int(intv)) {
return intTag, int(intv)
return yaml_INT_TAG, int(intv)
} else {
return intTag, intv
}
}
}
// Octals as introduced in version 1.2 of the spec.
// Octals from the 1.1 spec, spelled as 0777, are still
// decoded by default in v3 as well for compatibility.
// May be dropped in v4 depending on how usage evolves.
if strings.HasPrefix(plain, "0o") {
intv, err := strconv.ParseInt(plain[2:], 8, 64)
if err == nil {
if intv == int64(int(intv)) {
return intTag, int(intv)
} else {
return intTag, intv
}
}
uintv, err := strconv.ParseUint(plain[2:], 8, 64)
if err == nil {
return intTag, uintv
}
} else if strings.HasPrefix(plain, "-0o") {
intv, err := strconv.ParseInt("-"+plain[3:], 8, 64)
if err == nil {
if true || intv == int64(int(intv)) {
return intTag, int(intv)
} else {
return intTag, intv
return yaml_INT_TAG, intv
}
}
}
default:
panic("internal error: missing handler for resolver table: " + string(rune(hint)) + " (with " + in + ")")
panic("resolveTable item not yet handled: " + string(rune(hint)) + " (with " + in + ")")
}
}
return strTag, in
return yaml_STR_TAG, in
}
// encodeBase64 encodes s as base64 that is broken up into multiple lines

View File

@@ -1,25 +1,3 @@
//
// Copyright (c) 2011-2019 Canonical Ltd
// Copyright (c) 2006-2010 Kirill Simonov
//
// 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.
package yaml
import (
@@ -651,11 +629,8 @@ func yaml_parser_fetch_more_tokens(parser *yaml_parser_t) bool {
// Check if we really need to fetch more tokens.
need_more_tokens := false
// [Go] When parsing flow items, force the queue to have at least
// two items so that comments after commas may be associated
// with the value being parsed before them.
if parser.tokens_head == len(parser.tokens) || parser.flow_level > 0 && parser.tokens_head >= len(parser.tokens)-1 {
// Queue is empty or has just one element inside a flow context.
if parser.tokens_head == len(parser.tokens) {
// Queue is empty.
need_more_tokens = true
} else {
// Check if any potential simple key may occupy the head position.
@@ -687,7 +662,7 @@ func yaml_parser_fetch_more_tokens(parser *yaml_parser_t) bool {
}
// The dispatcher for token fetchers.
func yaml_parser_fetch_next_token(parser *yaml_parser_t) (ok bool) {
func yaml_parser_fetch_next_token(parser *yaml_parser_t) bool {
// Ensure that the buffer is initialized.
if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) {
return false
@@ -742,25 +717,6 @@ func yaml_parser_fetch_next_token(parser *yaml_parser_t) (ok bool) {
return yaml_parser_fetch_document_indicator(parser, yaml_DOCUMENT_END_TOKEN)
}
comment_mark := parser.mark
if parser.flow_level > 0 && buf[pos] == ',' && len(parser.tokens) > 0 {
// Associate any following comments with the prior token.
comment_mark = parser.tokens[len(parser.tokens)-1].start_mark
}
defer func() {
if !ok {
return
}
if !yaml_parser_scan_line_comment(parser, comment_mark) {
ok = false
return
}
if !yaml_parser_scan_foot_comment(parser, comment_mark) {
ok = false
return
}
}()
// Is it the flow sequence start indicator?
if buf[pos] == '[' {
return yaml_parser_fetch_flow_collection_start(parser, yaml_FLOW_SEQUENCE_START_TOKEN)
@@ -854,7 +810,7 @@ func yaml_parser_fetch_next_token(parser *yaml_parser_t) (ok bool) {
// if it is followed by a non-space character.
//
// The last rule is more restrictive than the specification requires.
// [Go] TODO Make this logic more reasonable.
// [Go] Make this logic more reasonable.
//switch parser.buffer[parser.buffer_pos] {
//case '-', '?', ':', ',', '?', '-', ',', ':', ']', '[', '}', '{', '&', '#', '!', '*', '>', '|', '"', '\'', '@', '%', '-', '`':
//}
@@ -1141,7 +1097,6 @@ func yaml_parser_fetch_document_indicator(parser *yaml_parser_t, typ yaml_token_
// Produce the FLOW-SEQUENCE-START or FLOW-MAPPING-START token.
func yaml_parser_fetch_flow_collection_start(parser *yaml_parser_t, typ yaml_token_type_t) bool {
// The indicators '[' and '{' may start a simple key.
if !yaml_parser_save_simple_key(parser) {
return false
@@ -1500,8 +1455,11 @@ func yaml_parser_scan_to_next_token(parser *yaml_parser_t) bool {
// Eat a comment until a line break.
if parser.buffer[parser.buffer_pos] == '#' {
if !yaml_parser_scan_head_comment(parser, parser.mark) {
return false
for !is_breakz(parser.buffer, parser.buffer_pos) {
skip(parser)
if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) {
return false
}
}
}
@@ -1599,10 +1557,6 @@ func yaml_parser_scan_directive(parser *yaml_parser_t, token *yaml_token_t) bool
}
if parser.buffer[parser.buffer_pos] == '#' {
// [Go] Discard this inline comment for the time being.
//if !yaml_parser_scan_line_comment(parser, start_mark) {
// return false
//}
for !is_breakz(parser.buffer, parser.buffer_pos) {
skip(parser)
if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) {
@@ -2018,7 +1972,7 @@ func yaml_parser_scan_tag_uri(parser *yaml_parser_t, directive bool, head []byte
// '0'-'9', 'A'-'Z', 'a'-'z', '_', '-', ';', '/', '?', ':', '@', '&',
// '=', '+', '$', ',', '.', '!', '~', '*', '\'', '(', ')', '[', ']',
// '%'.
// [Go] TODO Convert this into more reasonable logic.
// [Go] Convert this into more reasonable logic.
for is_alpha(parser.buffer, parser.buffer_pos) || parser.buffer[parser.buffer_pos] == ';' ||
parser.buffer[parser.buffer_pos] == '/' || parser.buffer[parser.buffer_pos] == '?' ||
parser.buffer[parser.buffer_pos] == ':' || parser.buffer[parser.buffer_pos] == '@' ||
@@ -2173,8 +2127,11 @@ func yaml_parser_scan_block_scalar(parser *yaml_parser_t, token *yaml_token_t, l
}
}
if parser.buffer[parser.buffer_pos] == '#' {
if !yaml_parser_scan_line_comment(parser, start_mark) {
return false
for !is_breakz(parser.buffer, parser.buffer_pos) {
skip(parser)
if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) {
return false
}
}
}
@@ -2737,159 +2694,3 @@ func yaml_parser_scan_plain_scalar(parser *yaml_parser_t, token *yaml_token_t) b
}
return true
}
func yaml_parser_scan_line_comment(parser *yaml_parser_t, after yaml_mark_t) bool {
if parser.mark.column == 0 {
return true
}
parser.comments = append(parser.comments, yaml_comment_t{after: after})
comment := &parser.comments[len(parser.comments)-1].line
for peek := 0; peek < 512; peek++ {
if parser.unread < peek+1 && !yaml_parser_update_buffer(parser, peek+1) {
break
}
if is_blank(parser.buffer, parser.buffer_pos+peek) {
continue
}
if parser.buffer[parser.buffer_pos+peek] == '#' {
if len(*comment) > 0 {
*comment = append(*comment, '\n')
}
for !is_breakz(parser.buffer, parser.buffer_pos+peek) {
*comment = append(*comment, parser.buffer[parser.buffer_pos+peek])
peek++
if parser.unread < peek+1 && !yaml_parser_update_buffer(parser, peek+1) {
return false
}
}
// Skip until after the consumed comment line.
until := parser.buffer_pos + peek
for parser.buffer_pos < until {
if is_break(parser.buffer, parser.buffer_pos) {
//break // Leave the break in the buffer so calling this function twice is safe.
if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) {
return false
}
skip_line(parser)
} else {
skip(parser)
}
}
}
break
}
return true
}
func yaml_parser_scan_head_comment(parser *yaml_parser_t, after yaml_mark_t) bool {
parser.comments = append(parser.comments, yaml_comment_t{after: after})
comment := &parser.comments[len(parser.comments)-1].head
breaks := false
for peek := 0; peek < 512; peek++ {
if parser.unread < peek+1 && !yaml_parser_update_buffer(parser, peek+1) {
break
}
if parser.buffer[parser.buffer_pos+peek] == 0 {
break
}
if is_blank(parser.buffer, parser.buffer_pos+peek) {
continue
}
if is_break(parser.buffer, parser.buffer_pos+peek) {
if !breaks {
*comment = append(*comment, '\n')
}
breaks = true
} else if parser.buffer[parser.buffer_pos+peek] == '#' {
if len(*comment) > 0 {
*comment = append(*comment, '\n')
}
breaks = false
for !is_breakz(parser.buffer, parser.buffer_pos+peek) {
*comment = append(*comment, parser.buffer[parser.buffer_pos+peek])
peek++
if parser.unread < peek+1 && !yaml_parser_update_buffer(parser, peek+1) {
return false
}
}
// Skip until after the consumed comment line.
until := parser.buffer_pos + peek
for parser.buffer_pos < until {
if is_break(parser.buffer, parser.buffer_pos) {
if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) {
return false
}
skip_line(parser)
} else {
skip(parser)
}
}
peek = 0
} else {
break
}
}
return true
}
func yaml_parser_scan_foot_comment(parser *yaml_parser_t, after yaml_mark_t) bool {
parser.comments = append(parser.comments, yaml_comment_t{after: after})
comment := &parser.comments[len(parser.comments)-1].foot
original := *comment
breaks := false
peek := 0
for ; peek < 32768; peek++ {
if parser.unread < peek+1 && !yaml_parser_update_buffer(parser, peek+1) {
break
}
c := parser.buffer[parser.buffer_pos+peek]
if c == 0 {
break
}
if is_blank(parser.buffer, parser.buffer_pos+peek) {
continue
}
if is_break(parser.buffer, parser.buffer_pos+peek) {
if breaks {
break
}
breaks = true
} else if c == '#' {
if len(*comment) > 0 {
*comment = append(*comment, '\n')
}
for !is_breakz(parser.buffer, parser.buffer_pos+peek) {
*comment = append(*comment, parser.buffer[parser.buffer_pos+peek])
peek++
if parser.unread < peek+1 && !yaml_parser_update_buffer(parser, peek+1) {
return false
}
}
breaks = true
} else if c == ']' || c == '}' {
break
} else {
// Abort and allow that next line to have the comment as its header.
*comment = original
return true
}
}
// Skip until after the consumed comment lines.
until := parser.buffer_pos + peek
for parser.buffer_pos < until {
if is_break(parser.buffer, parser.buffer_pos) {
if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) {
return false
}
skip_line(parser)
} else {
skip(parser)
}
}
return true
}

View File

@@ -1,18 +1,3 @@
//
// Copyright (c) 2011-2019 Canonical Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package yaml
import (
@@ -52,10 +37,8 @@ func (l keyList) Less(i, j int) bool {
return ak < bk
}
ar, br := []rune(a.String()), []rune(b.String())
digits := false
for i := 0; i < len(ar) && i < len(br); i++ {
if ar[i] == br[i] {
digits = unicode.IsDigit(ar[i])
continue
}
al := unicode.IsLetter(ar[i])
@@ -64,16 +47,12 @@ func (l keyList) Less(i, j int) bool {
return ar[i] < br[i]
}
if al || bl {
if digits {
return al
} else {
return bl
}
return bl
}
var ai, bi int
var an, bn int64
if ar[i] == '0' || br[i] == '0' {
for j := i - 1; j >= 0 && unicode.IsDigit(ar[j]); j-- {
for j := i-1; j >= 0 && unicode.IsDigit(ar[j]); j-- {
if ar[j] != '0' {
an = 1
bn = 1

26
vendor/gopkg.in/yaml.v2/writerc.go generated vendored Normal file
View File

@@ -0,0 +1,26 @@
package yaml
// Set the writer error and return false.
func yaml_emitter_set_writer_error(emitter *yaml_emitter_t, problem string) bool {
emitter.error = yaml_WRITER_ERROR
emitter.problem = problem
return false
}
// Flush the output buffer.
func yaml_emitter_flush(emitter *yaml_emitter_t) bool {
if emitter.write_handler == nil {
panic("write handler not set")
}
// Check if the buffer is empty.
if emitter.buffer_pos == 0 {
return true
}
if err := emitter.write_handler(emitter, emitter.buffer[:emitter.buffer_pos]); err != nil {
return yaml_emitter_set_writer_error(emitter, "write error: "+err.Error())
}
emitter.buffer_pos = 0
return true
}

View File

@@ -1,18 +1,3 @@
//
// Copyright (c) 2011-2019 Canonical Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package yaml implements YAML support for the Go language.
//
// Source code and other details for the project are available at GitHub:
@@ -28,16 +13,23 @@ import (
"reflect"
"strings"
"sync"
"unicode/utf8"
)
// The Unmarshaler interface may be implemented by types to customize their
// behavior when being unmarshaled from a YAML document.
type Unmarshaler interface {
UnmarshalYAML(value *Node) error
// MapSlice encodes and decodes as a YAML map.
// The order of keys is preserved when encoding and decoding.
type MapSlice []MapItem
// MapItem is an item in a MapSlice.
type MapItem struct {
Key, Value interface{}
}
type obsoleteUnmarshaler interface {
// The Unmarshaler interface may be implemented by types to customize their
// behavior when being unmarshaled from a YAML document. The UnmarshalYAML
// method receives a function that may be called to unmarshal the original
// YAML value into a field or variable. It is safe to call the unmarshal
// function parameter more than once if necessary.
type Unmarshaler interface {
UnmarshalYAML(unmarshal func(interface{}) error) error
}
@@ -89,10 +81,18 @@ func Unmarshal(in []byte, out interface{}) (err error) {
return unmarshal(in, out, false)
}
// UnmarshalStrict is like Unmarshal except that any fields that are found
// in the data that do not have corresponding struct members, or mapping
// keys that are duplicates, will result in
// an error.
func UnmarshalStrict(in []byte, out interface{}) (err error) {
return unmarshal(in, out, true)
}
// A Decorder reads and decodes YAML values from an input stream.
type Decoder struct {
parser *parser
knownFields bool
strict bool
parser *parser
}
// NewDecoder returns a new decoder that reads from r.
@@ -105,10 +105,10 @@ func NewDecoder(r io.Reader) *Decoder {
}
}
// KnownFields ensures that the keys in decoded mappings to
// exist as fields in the struct being decoded into.
func (dec *Decoder) KnownFields(enable bool) {
dec.knownFields = enable
// SetStrict sets whether strict decoding behaviour is enabled when
// decoding items in the data (see UnmarshalStrict). By default, decoding is not strict.
func (dec *Decoder) SetStrict(strict bool) {
dec.strict = strict
}
// Decode reads the next YAML-encoded value from its input
@@ -117,8 +117,7 @@ func (dec *Decoder) KnownFields(enable bool) {
// See the documentation for Unmarshal for details about the
// conversion of YAML into a Go value.
func (dec *Decoder) Decode(v interface{}) (err error) {
d := newDecoder()
d.knownFields = dec.knownFields
d := newDecoder(dec.strict)
defer handleErr(&err)
node := dec.parser.parse()
if node == nil {
@@ -135,27 +134,9 @@ func (dec *Decoder) Decode(v interface{}) (err error) {
return nil
}
// Decode decodes the node and stores its data into the value pointed to by v.
//
// See the documentation for Unmarshal for details about the
// conversion of YAML into a Go value.
func (n *Node) Decode(v interface{}) (err error) {
d := newDecoder()
defer handleErr(&err)
out := reflect.ValueOf(v)
if out.Kind() == reflect.Ptr && !out.IsNil() {
out = out.Elem()
}
d.unmarshal(n, out)
if len(d.terrors) > 0 {
return &TypeError{d.terrors}
}
return nil
}
func unmarshal(in []byte, out interface{}, strict bool) (err error) {
defer handleErr(&err)
d := newDecoder()
d := newDecoder(strict)
p := newParser(in)
defer p.destroy()
node := p.parse()
@@ -252,14 +233,6 @@ func (e *Encoder) Encode(v interface{}) (err error) {
return nil
}
// SetIndent changes the used indentation used when encoding.
func (e *Encoder) SetIndent(spaces int) {
if spaces < 0 {
panic("yaml: cannot indent to a negative number of spaces")
}
e.encoder.indent = spaces
}
// Close closes the encoder by writing any remaining data.
// It does not write a stream terminating string "...".
func (e *Encoder) Close() (err error) {
@@ -302,150 +275,6 @@ func (e *TypeError) Error() string {
return fmt.Sprintf("yaml: unmarshal errors:\n %s", strings.Join(e.Errors, "\n "))
}
type Kind uint32
const (
DocumentNode Kind = 1 << iota
SequenceNode
MappingNode
ScalarNode
AliasNode
)
type Style uint32
const (
TaggedStyle Style = 1 << iota
DoubleQuotedStyle
SingleQuotedStyle
LiteralStyle
FoldedStyle
FlowStyle
)
// Node represents an element in the YAML document hierarchy. While documents
// are typically encoded and decoded into higher level types, such as structs
// and maps, Node is an intermediate representation that allows detailed
// control over the content being decoded or encoded.
//
// Values that make use of the Node type interact with the yaml package in the
// same way any other type would do, by encoding and decoding yaml data
// directly or indirectly into them.
//
// For example:
//
// var person struct {
// Name string
// Address yaml.Node
// }
// err := yaml.Unmarshal(data, &person)
//
// Or by itself:
//
// var person Node
// err := yaml.Unmarshal(data, &person)
//
type Node struct {
// Kind defines whether the node is a document, a mapping, a sequence,
// a scalar value, or an alias to another node. The specific data type of
// scalar nodes may be obtained via the ShortTag and LongTag methods.
Kind Kind
// Style allows customizing the apperance of the node in the tree.
Style Style
// Tag holds the YAML tag defining the data type for the value.
// When decoding, this field will always be set to the resolved tag,
// even when it wasn't explicitly provided in the YAML content.
// When encoding, if this field is unset the value type will be
// implied from the node properties, and if it is set, it will only
// be serialized into the representation if TaggedStyle is used or
// the implicit tag diverges from the provided one.
Tag string
// Value holds the unescaped and unquoted represenation of the value.
Value string
// Anchor holds the anchor name for this node, which allows aliases to point to it.
Anchor string
// Alias holds the node that this alias points to. Only valid when Kind is AliasNode.
Alias *Node
// Content holds contained nodes for documents, mappings, and sequences.
Content []*Node
// HeadComment holds any comments in the lines preceding the node and
// not separated by an empty line.
HeadComment string
// LineComment holds any comments at the end of the line where the node is in.
LineComment string
// FootComment holds any comments following the node and before empty lines.
FootComment string
// Line and Column hold the node position in the decoded YAML text.
// These fields are not respected when encoding the node.
Line int
Column int
}
// LongTag returns the long form of the tag that indicates the data type for
// the node. If the Tag field isn't explicitly defined, one will be computed
// based on the node properties.
func (n *Node) LongTag() string {
return longTag(n.ShortTag())
}
// ShortTag returns the short form of the YAML tag that indicates data type for
// the node. If the Tag field isn't explicitly defined, one will be computed
// based on the node properties.
func (n *Node) ShortTag() string {
if n.indicatedString() {
return strTag
}
if n.Tag == "" || n.Tag == "!" {
switch n.Kind {
case MappingNode:
return mapTag
case SequenceNode:
return seqTag
case AliasNode:
if n.Alias != nil {
return n.Alias.ShortTag()
}
case ScalarNode:
tag, _ := resolve("", n.Value)
return tag
}
return ""
}
return shortTag(n.Tag)
}
func (n *Node) indicatedString() bool {
return n.Kind == ScalarNode &&
(shortTag(n.Tag) == strTag ||
(n.Tag == "" || n.Tag == "!") && n.Style&(SingleQuotedStyle|DoubleQuotedStyle|LiteralStyle|FoldedStyle) != 0)
}
// SetString is a convenience function that sets the node to a string value
// and defines its style in a pleasant way depending on its content.
func (n *Node) SetString(s string) {
n.Kind = ScalarNode
if utf8.ValidString(s) {
n.Value = s
n.Tag = strTag
} else {
n.Value = encodeBase64(s)
n.Tag = binaryTag
}
if strings.Contains(n.Value, "\n") {
n.Style = LiteralStyle
}
}
// --------------------------------------------------------------------------
// Maintain a mapping of keys to structure field indexes
@@ -460,10 +289,6 @@ type structInfo struct {
// InlineMap is the number of the field in the struct that
// contains an ,inline map, or -1 if there's none.
InlineMap int
// InlineUnmarshalers holds indexes to inlined fields that
// contain unmarshaler values.
InlineUnmarshalers [][]int
}
type fieldInfo struct {
@@ -481,12 +306,6 @@ type fieldInfo struct {
var structMap = make(map[reflect.Type]*structInfo)
var fieldMapMutex sync.RWMutex
var unmarshalerType reflect.Type
func init() {
var v Unmarshaler
unmarshalerType = reflect.ValueOf(&v).Elem().Type()
}
func getStructInfo(st reflect.Type) (*structInfo, error) {
fieldMapMutex.RLock()
@@ -500,7 +319,6 @@ func getStructInfo(st reflect.Type) (*structInfo, error) {
fieldsMap := make(map[string]fieldInfo)
fieldsList := make([]fieldInfo, 0, n)
inlineMap := -1
inlineUnmarshalers := [][]int(nil)
for i := 0; i != n; i++ {
field := st.Field(i)
if field.PkgPath != "" && !field.Anonymous {
@@ -529,7 +347,7 @@ func getStructInfo(st reflect.Type) (*structInfo, error) {
case "inline":
inline = true
default:
return nil, errors.New(fmt.Sprintf("unsupported flag %q in tag %q of type %s", flag, tag, st))
return nil, errors.New(fmt.Sprintf("Unsupported flag %q in tag %q of type %s", flag, tag, st))
}
}
tag = fields[0]
@@ -539,47 +357,34 @@ func getStructInfo(st reflect.Type) (*structInfo, error) {
switch field.Type.Kind() {
case reflect.Map:
if inlineMap >= 0 {
return nil, errors.New("multiple ,inline maps in struct " + st.String())
return nil, errors.New("Multiple ,inline maps in struct " + st.String())
}
if field.Type.Key() != reflect.TypeOf("") {
return nil, errors.New("option ,inline needs a map with string keys in struct " + st.String())
return nil, errors.New("Option ,inline needs a map with string keys in struct " + st.String())
}
inlineMap = info.Num
case reflect.Struct, reflect.Ptr:
ftype := field.Type
for ftype.Kind() == reflect.Ptr {
ftype = ftype.Elem()
case reflect.Struct:
sinfo, err := getStructInfo(field.Type)
if err != nil {
return nil, err
}
if ftype.Kind() != reflect.Struct {
return nil, errors.New("option ,inline may only be used on a struct or map field")
}
if reflect.PtrTo(ftype).Implements(unmarshalerType) {
inlineUnmarshalers = append(inlineUnmarshalers, []int{i})
} else {
sinfo, err := getStructInfo(ftype)
if err != nil {
return nil, err
for _, finfo := range sinfo.FieldsList {
if _, found := fieldsMap[finfo.Key]; found {
msg := "Duplicated key '" + finfo.Key + "' in struct " + st.String()
return nil, errors.New(msg)
}
for _, index := range sinfo.InlineUnmarshalers {
inlineUnmarshalers = append(inlineUnmarshalers, append([]int{i}, index...))
}
for _, finfo := range sinfo.FieldsList {
if _, found := fieldsMap[finfo.Key]; found {
msg := "duplicated key '" + finfo.Key + "' in struct " + st.String()
return nil, errors.New(msg)
}
if finfo.Inline == nil {
finfo.Inline = []int{i, finfo.Num}
} else {
finfo.Inline = append([]int{i}, finfo.Inline...)
}
finfo.Id = len(fieldsList)
fieldsMap[finfo.Key] = finfo
fieldsList = append(fieldsList, finfo)
if finfo.Inline == nil {
finfo.Inline = []int{i, finfo.Num}
} else {
finfo.Inline = append([]int{i}, finfo.Inline...)
}
finfo.Id = len(fieldsList)
fieldsMap[finfo.Key] = finfo
fieldsList = append(fieldsList, finfo)
}
default:
return nil, errors.New("option ,inline may only be used on a struct or map field")
//return nil, errors.New("Option ,inline needs a struct value or map field")
return nil, errors.New("Option ,inline needs a struct value field")
}
continue
}
@@ -591,7 +396,7 @@ func getStructInfo(st reflect.Type) (*structInfo, error) {
}
if _, found = fieldsMap[info.Key]; found {
msg := "duplicated key '" + info.Key + "' in struct " + st.String()
msg := "Duplicated key '" + info.Key + "' in struct " + st.String()
return nil, errors.New(msg)
}
@@ -601,10 +406,9 @@ func getStructInfo(st reflect.Type) (*structInfo, error) {
}
sinfo = &structInfo{
FieldsMap: fieldsMap,
FieldsList: fieldsList,
InlineMap: inlineMap,
InlineUnmarshalers: inlineUnmarshalers,
FieldsMap: fieldsMap,
FieldsList: fieldsList,
InlineMap: inlineMap,
}
fieldMapMutex.Lock()

View File

@@ -1,25 +1,3 @@
//
// Copyright (c) 2011-2019 Canonical Ltd
// Copyright (c) 2006-2010 Kirill Simonov
//
// 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.
package yaml
import (
@@ -95,13 +73,13 @@ type yaml_scalar_style_t yaml_style_t
// Scalar styles.
const (
// Let the emitter choose the style.
yaml_ANY_SCALAR_STYLE yaml_scalar_style_t = 0
yaml_ANY_SCALAR_STYLE yaml_scalar_style_t = iota
yaml_PLAIN_SCALAR_STYLE yaml_scalar_style_t = 1 << iota // The plain scalar style.
yaml_SINGLE_QUOTED_SCALAR_STYLE // The single-quoted scalar style.
yaml_DOUBLE_QUOTED_SCALAR_STYLE // The double-quoted scalar style.
yaml_LITERAL_SCALAR_STYLE // The literal scalar style.
yaml_FOLDED_SCALAR_STYLE // The folded scalar style.
yaml_PLAIN_SCALAR_STYLE // The plain scalar style.
yaml_SINGLE_QUOTED_SCALAR_STYLE // The single-quoted scalar style.
yaml_DOUBLE_QUOTED_SCALAR_STYLE // The double-quoted scalar style.
yaml_LITERAL_SCALAR_STYLE // The literal scalar style.
yaml_FOLDED_SCALAR_STYLE // The folded scalar style.
)
type yaml_sequence_style_t yaml_style_t
@@ -301,11 +279,6 @@ type yaml_event_t struct {
// The list of tag directives (for yaml_DOCUMENT_START_EVENT).
tag_directives []yaml_tag_directive_t
// The comments
head_comment []byte
line_comment []byte
foot_comment []byte
// The anchor (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT, yaml_ALIAS_EVENT).
anchor []byte
@@ -589,15 +562,6 @@ type yaml_parser_t struct {
offset int // The offset of the current position (in bytes).
mark yaml_mark_t // The mark of the current position.
// Comments
head_comment []byte // The current head comments
line_comment []byte // The current line comments
foot_comment []byte // The current foot comments
comments []yaml_comment_t // The folded comments for all parsed tokens
comments_head int
// Scanner stuff
stream_start_produced bool // Have we started to scan the input stream?
@@ -630,13 +594,6 @@ type yaml_parser_t struct {
document *yaml_document_t // The currently parsed document.
}
type yaml_comment_t struct {
after yaml_mark_t
head []byte
line []byte
foot []byte
}
// Emitter Definitions
// The prototype of a write handler.
@@ -667,10 +624,8 @@ const (
yaml_EMIT_DOCUMENT_CONTENT_STATE // Expect the content of a document.
yaml_EMIT_DOCUMENT_END_STATE // Expect DOCUMENT-END.
yaml_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE // Expect the first item of a flow sequence.
yaml_EMIT_FLOW_SEQUENCE_TRAIL_ITEM_STATE // Expect the next item of a flow sequence, with the comma already written out
yaml_EMIT_FLOW_SEQUENCE_ITEM_STATE // Expect an item of a flow sequence.
yaml_EMIT_FLOW_MAPPING_FIRST_KEY_STATE // Expect the first key of a flow mapping.
yaml_EMIT_FLOW_MAPPING_TRAIL_KEY_STATE // Expect the next key of a flow mapping, with the comma already written out
yaml_EMIT_FLOW_MAPPING_KEY_STATE // Expect a key of a flow mapping.
yaml_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE // Expect a value for a simple key of a flow mapping.
yaml_EMIT_FLOW_MAPPING_VALUE_STATE // Expect a value of a flow mapping.
@@ -742,8 +697,6 @@ type yaml_emitter_t struct {
indention bool // If the last character was an indentation character (' ', '-', '?', ':')?
open_ended bool // If an explicit document end is required?
space_above bool // If there's an empty line right above?
// Anchor analysis.
anchor_data struct {
anchor []byte // The anchor value.
@@ -767,11 +720,6 @@ type yaml_emitter_t struct {
style yaml_scalar_style_t // The output style.
}
// Comments
head_comment []byte
line_comment []byte
foot_comment []byte
// Dumper stuff
opened bool // If the stream was already opened?

View File

@@ -1,25 +1,3 @@
//
// Copyright (c) 2011-2019 Canonical Ltd
// Copyright (c) 2006-2010 Kirill Simonov
//
// 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.
package yaml
const (

15
vendor/gopkg.in/yaml.v3/.travis.yml generated vendored
View File

@@ -1,15 +0,0 @@
language: go
go:
- "1.4"
- "1.5"
- "1.6"
- "1.7"
- "1.8"
- "1.9"
- "1.10"
- "1.11"
- "1.12"
- tip
go_import_path: gopkg.in/yaml.v2

48
vendor/gopkg.in/yaml.v3/writerc.go generated vendored
View File

@@ -1,48 +0,0 @@
//
// Copyright (c) 2011-2019 Canonical Ltd
// Copyright (c) 2006-2010 Kirill Simonov
//
// 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.
package yaml
// Set the writer error and return false.
func yaml_emitter_set_writer_error(emitter *yaml_emitter_t, problem string) bool {
emitter.error = yaml_WRITER_ERROR
emitter.problem = problem
return false
}
// Flush the output buffer.
func yaml_emitter_flush(emitter *yaml_emitter_t) bool {
if emitter.write_handler == nil {
panic("write handler not set")
}
// Check if the buffer is empty.
if emitter.buffer_pos == 0 {
return true
}
if err := emitter.write_handler(emitter, emitter.buffer[:emitter.buffer_pos]); err != nil {
return yaml_emitter_set_writer_error(emitter, "write error: "+err.Error())
}
emitter.buffer_pos = 0
return true
}

4
vendor/modules.txt vendored
View File

@@ -36,8 +36,8 @@ golang.org/x/sync/errgroup
# golang.org/x/sys v0.0.0-20180831094639-fa5fdf94c789
golang.org/x/sys/unix
golang.org/x/sys/windows
# gopkg.in/yaml.v3 v3.0.0-20190409140830-cdc409dda467
gopkg.in/yaml.v3
# gopkg.in/yaml.v2 v2.2.1
gopkg.in/yaml.v2
# mvdan.cc/sh v2.6.4+incompatible
mvdan.cc/sh/expand
mvdan.cc/sh/interp

View File

@@ -8,6 +8,7 @@ import (
"syscall"
"time"
"github.com/go-task/task/v2/internal/logger"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/mattn/go-zglob"
"github.com/radovskyb/watcher"
@@ -24,14 +25,14 @@ func (e *Executor) watchTasks(calls ...taskfile.Call) error {
for i, c := range calls {
tasks[i] = c.Task
}
e.Logger.Errf("task: Started watching for tasks: %s", strings.Join(tasks, ", "))
e.Logger.Errf(logger.Green, "task: Started watching for tasks: %s", strings.Join(tasks, ", "))
ctx, cancel := context.WithCancel(context.Background())
for _, c := range calls {
c := c
go func() {
if err := e.RunTask(ctx, c); err != nil && !isContextError(err) {
e.Logger.Errf("%v", err)
e.Logger.Errf(logger.Red, "%v", err)
}
}()
}
@@ -49,7 +50,7 @@ func (e *Executor) watchTasks(calls ...taskfile.Call) error {
for {
select {
case event := <-w.Event:
e.Logger.VerboseErrf("task: received watch event: %v", event)
e.Logger.VerboseErrf(logger.Magenta, "task: received watch event: %v", event)
cancel()
ctx, cancel = context.WithCancel(context.Background())
@@ -57,7 +58,7 @@ func (e *Executor) watchTasks(calls ...taskfile.Call) error {
c := c
go func() {
if err := e.RunTask(ctx, c); err != nil && !isContextError(err) {
e.Logger.Errf("%v", err)
e.Logger.Errf(logger.Red, "%v", err)
}
}()
}
@@ -68,7 +69,7 @@ func (e *Executor) watchTasks(calls ...taskfile.Call) error {
w.TriggerEvent(watcher.Remove, nil)
}()
default:
e.Logger.Errf("%v", err)
e.Logger.Errf(logger.Red, "%v", err)
}
case <-w.Closed:
cancel()
@@ -81,7 +82,7 @@ func (e *Executor) watchTasks(calls ...taskfile.Call) error {
// re-register each second because we can have new files
for {
if err := e.registerWatchedFiles(w, calls...); err != nil {
e.Logger.Errf("%v", err)
e.Logger.Errf(logger.Red, "%v", err)
}
time.Sleep(time.Second)
}