diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..95f611ea --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,26 @@ +name: goreleaser + +on: + push: + tags: + - '*' + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v1 + + - name: Set up Go + uses: actions/setup-go@v1 + with: + go-version: 1.14.x + + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v1 + with: + version: latest + args: release --rm-dist + env: + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c8ec8c2c..d797964e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,7 +5,7 @@ jobs: name: Test strategy: matrix: - go-version: [1.12.x, 1.13.x] + go-version: [1.13.x, 1.14.x] platform: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{matrix.platform}} steps: diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index bf669e5a..00000000 --- a/.travis.yml +++ /dev/null @@ -1,17 +0,0 @@ -language: go - -go: - - 1.12.x - - 1.13.x - -env: - - GO111MODULE=on - -addons: - apt: - packages: - - rpm - -script: - - go install -v ./cmd/task - - task ci diff --git a/cmd/redirector/redirector.go b/cmd/redirector/redirector.go deleted file mode 100644 index 2aa6023a..00000000 --- a/cmd/redirector/redirector.go +++ /dev/null @@ -1,421 +0,0 @@ -// This small web app is used to redirect from the old taskfile.org domain -// to the new taskfile.dev without breaking CIs that uses cURL to download -// "/install.sh" without the -L flag (which follow redirects). - -package main - -import ( - "net/http" -) - -func main() { - http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == "/install.sh" { - println("Dumping install.sh") - - w.Write(installShContent) - return - } - - println("Redirecting to https://taskfile.dev" + r.URL.Path) - - w.Header().Set("Location", "https://taskfile.dev"+r.URL.Path) - w.WriteHeader(301) - }) - - println("Listening :8080") - - panic(http.ListenAndServe(":8080", nil)) -} - -var installShContent = []byte(`#!/bin/sh -set -e -# Code generated by godownloader on 2018-04-07T17:47:38Z. DO NOT EDIT. -# - -usage() { - this=$1 - cat </dev/null -} -echoerr() { - echo "$@" 1>&2 -} -log_prefix() { - echo "$0" -} -_logp=6 -log_set_priority() { - _logp="$1" -} -log_priority() { - if test -z "$1"; then - echo "$_logp" - return - fi - [ "$1" -le "$_logp" ] -} -log_tag() { - case $1 in - 0) echo "emerg" ;; - 1) echo "alert" ;; - 2) echo "crit" ;; - 3) echo "err" ;; - 4) echo "warning" ;; - 5) echo "notice" ;; - 6) echo "info" ;; - 7) echo "debug" ;; - *) echo "$1" ;; - esac -} -log_debug() { - log_priority 7 || return 0 - echoerr "$(log_prefix)" "$(log_tag 7)" "$@" -} -log_info() { - log_priority 6 || return 0 - echoerr "$(log_prefix)" "$(log_tag 6)" "$@" -} -log_err() { - log_priority 3 || return 0 - echoerr "$(log_prefix)" "$(log_tag 3)" "$@" -} -log_crit() { - log_priority 2 || return 0 - echoerr "$(log_prefix)" "$(log_tag 2)" "$@" -} -uname_os() { - os=$(uname -s | tr '[:upper:]' '[:lower:]') - case "$os" in - msys_nt) os="windows" ;; - esac - echo "$os" -} -uname_arch() { - arch=$(uname -m) - case $arch in - x86_64) arch="amd64" ;; - x86) arch="386" ;; - i686) arch="386" ;; - i386) arch="386" ;; - aarch64) arch="arm64" ;; - armv5*) arch="arm5" ;; - armv6*) arch="arm6" ;; - armv7*) arch="arm7" ;; - esac - echo ${arch} -} -uname_os_check() { - os=$(uname_os) - case "$os" in - darwin) return 0 ;; - dragonfly) return 0 ;; - freebsd) return 0 ;; - linux) return 0 ;; - android) return 0 ;; - nacl) return 0 ;; - netbsd) return 0 ;; - openbsd) return 0 ;; - plan9) return 0 ;; - solaris) return 0 ;; - windows) return 0 ;; - esac - log_crit "uname_os_check '$(uname -s)' got converted to '$os' which is not a GOOS value. Please file bug at https://github.com/client9/shlib" - return 1 -} -uname_arch_check() { - arch=$(uname_arch) - case "$arch" in - 386) return 0 ;; - amd64) return 0 ;; - arm64) return 0 ;; - armv5) return 0 ;; - armv6) return 0 ;; - armv7) return 0 ;; - ppc64) return 0 ;; - ppc64le) return 0 ;; - mips) return 0 ;; - mipsle) return 0 ;; - mips64) return 0 ;; - mips64le) return 0 ;; - s390x) return 0 ;; - amd64p32) return 0 ;; - esac - log_crit "uname_arch_check '$(uname -m)' got converted to '$arch' which is not a GOARCH value. Please file bug report at https://github.com/client9/shlib" - return 1 -} -untar() { - tarball=$1 - case "${tarball}" in - *.tar.gz | *.tgz) tar -xzf "${tarball}" ;; - *.tar) tar -xf "${tarball}" ;; - *.zip) unzip "${tarball}" ;; - *) - log_err "untar unknown archive format for ${tarball}" - return 1 - ;; - esac -} -mktmpdir() { - test -z "$TMPDIR" && TMPDIR="$(mktemp -d)" - mkdir -p "${TMPDIR}" - echo "${TMPDIR}" -} -http_download_curl() { - local_file=$1 - source_url=$2 - header=$3 - if [ -z "$header" ]; then - code=$(curl -w '%{http_code}' -sL -o "$local_file" "$source_url") - else - code=$(curl -w '%{http_code}' -sL -H "$header" -o "$local_file" "$source_url") - fi - if [ "$code" != "200" ]; then - log_debug "http_download_curl received HTTP status $code" - return 1 - fi - return 0 -} -http_download_wget() { - local_file=$1 - source_url=$2 - header=$3 - if [ -z "$header" ]; then - wget -q -O "$local_file" "$source_url" - else - wget -q --header "$header" -O "$local_file" "$source_url" - fi -} -http_download() { - log_debug "http_download $2" - if is_command curl; then - http_download_curl "$@" - return - elif is_command wget; then - http_download_wget "$@" - return - fi - log_crit "http_download unable to find wget or curl" - return 1 -} -http_copy() { - tmp=$(mktemp) - http_download "${tmp}" "$1" "$2" || return 1 - body=$(cat "$tmp") - rm -f "${tmp}" - echo "$body" -} -github_release() { - owner_repo=$1 - version=$2 - test -z "$version" && version="latest" - giturl="https://github.com/${owner_repo}/releases/${version}" - json=$(http_copy "$giturl" "Accept:application/json") - test -z "$json" && return 1 - version=$(echo "$json" | tr -s '\n' ' ' | sed 's/.*"tag_name":"//' | sed 's/".*//') - test -z "$version" && return 1 - echo "$version" -} -hash_sha256() { - TARGET=${1:-/dev/stdin} - if is_command gsha256sum; then - hash=$(gsha256sum "$TARGET") || return 1 - echo "$hash" | cut -d ' ' -f 1 - elif is_command sha256sum; then - hash=$(sha256sum "$TARGET") || return 1 - echo "$hash" | cut -d ' ' -f 1 - elif is_command shasum; then - hash=$(shasum -a 256 "$TARGET" 2>/dev/null) || return 1 - echo "$hash" | cut -d ' ' -f 1 - elif is_command openssl; then - hash=$(openssl -dst openssl dgst -sha256 "$TARGET") || return 1 - echo "$hash" | cut -d ' ' -f a - else - log_crit "hash_sha256 unable to find command to compute sha-256 hash" - return 1 - fi -} -hash_sha256_verify() { - TARGET=$1 - checksums=$2 - if [ -z "$checksums" ]; then - log_err "hash_sha256_verify checksum file not specified in arg2" - return 1 - fi - BASENAME=${TARGET##*/} - want=$(grep "${BASENAME}" "${checksums}" 2>/dev/null | tr '\t' ' ' | cut -d ' ' -f 1) - if [ -z "$want" ]; then - log_err "hash_sha256_verify unable to find checksum for '${TARGET}' in '${checksums}'" - return 1 - fi - got=$(hash_sha256 "$TARGET") - if [ "$want" != "$got" ]; then - log_err "hash_sha256_verify checksum for '$TARGET' did not verify ${want} vs $got" - return 1 - fi -} -cat /dev/null < 0 if !spaceRequired { return diff --git a/task.go b/task.go index 5c4f07d6..be6740ca 100644 --- a/task.go +++ b/task.go @@ -70,7 +70,14 @@ func (e *Executor) Run(ctx context.Context, calls ...taskfile.Call) error { } if e.Summary { - summary.PrintTasks(e.Logger, e.Taskfile, calls) + for i, c := range calls { + compiledTask, err := e.CompiledTask(c) + if err != nil { + return nil + } + summary.PrintSpaceBetweenSummaries(e.Logger, i) + summary.PrintTask(e.Logger, compiledTask) + } return nil } diff --git a/variables.go b/variables.go index 7caeb355..d3160171 100644 --- a/variables.go +++ b/variables.go @@ -28,6 +28,7 @@ func (e *Executor) CompiledTask(call taskfile.Call) (*taskfile.Task, error) { new := taskfile.Task{ Task: origTask.Task, Desc: r.Replace(origTask.Desc), + Summary: r.Replace(origTask.Summary), Sources: r.ReplaceSlice(origTask.Sources), Generates: r.ReplaceSlice(origTask.Generates), Dir: r.Replace(origTask.Dir), diff --git a/vendor/modules.txt b/vendor/modules.txt index 128c3fc0..faebaa02 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -37,7 +37,7 @@ golang.org/x/xerrors golang.org/x/xerrors/internal # gopkg.in/yaml.v2 v2.2.2 gopkg.in/yaml.v2 -# mvdan.cc/sh/v3 v3.0.0-beta1 +# mvdan.cc/sh/v3 v3.0.2 mvdan.cc/sh/v3/expand mvdan.cc/sh/v3/interp mvdan.cc/sh/v3/pattern diff --git a/vendor/mvdan.cc/sh/v3/expand/braces.go b/vendor/mvdan.cc/sh/v3/expand/braces.go index 482f8116..9914fe44 100644 --- a/vendor/mvdan.cc/sh/v3/expand/braces.go +++ b/vendor/mvdan.cc/sh/v3/expand/braces.go @@ -24,13 +24,13 @@ func Braces(word *syntax.Word) []*syntax.Word { continue } if br.Sequence { - var from, to int - if br.Chars { + chars := false + from, err1 := strconv.Atoi(br.Elems[0].Lit()) + to, err2 := strconv.Atoi(br.Elems[1].Lit()) + if err1 != nil || err2 != nil { + chars = true from = int(br.Elems[0].Lit()[0]) to = int(br.Elems[1].Lit()[0]) - } else { - from, _ = strconv.Atoi(br.Elems[0].Lit()) - to, _ = strconv.Atoi(br.Elems[1].Lit()) } upward := from <= to incr := 1 @@ -54,7 +54,7 @@ func Braces(word *syntax.Word) []*syntax.Word { next := *word next.Parts = next.Parts[i+1:] lit := &syntax.Lit{} - if br.Chars { + if chars { lit.Value = string(n) } else { lit.Value = strconv.Itoa(n) diff --git a/vendor/mvdan.cc/sh/v3/expand/expand.go b/vendor/mvdan.cc/sh/v3/expand/expand.go index 0186a8fc..847cd46d 100644 --- a/vendor/mvdan.cc/sh/v3/expand/expand.go +++ b/vendor/mvdan.cc/sh/v3/expand/expand.go @@ -159,6 +159,8 @@ func Document(cfg *Config, word *syntax.Word) (string, error) { return cfg.fieldJoin(field), nil } +const patMode = pattern.Filenames | pattern.Braces + // Pattern expands a single shell word as a pattern, using syntax.QuotePattern // on any non-quoted parts of the input word. The result can be used on // syntax.TranslatePattern directly. @@ -174,7 +176,7 @@ func Pattern(cfg *Config, word *syntax.Word) (string, error) { buf := cfg.strBuilder() for _, part := range field { if part.quote > quoteNone { - buf.WriteString(pattern.QuoteMeta(part.val)) + buf.WriteString(pattern.QuoteMeta(part.val, patMode)) } else { buf.WriteString(part.val) } @@ -345,11 +347,11 @@ func (cfg *Config) escapedGlobField(parts []fieldPart) (escaped string, glob boo buf := cfg.strBuilder() for _, part := range parts { if part.quote > quoteNone { - buf.WriteString(pattern.QuoteMeta(part.val)) + buf.WriteString(pattern.QuoteMeta(part.val, patMode)) continue } buf.WriteString(part.val) - if pattern.HasMeta(part.val) { + if pattern.HasMeta(part.val, patMode) { glob = true } } @@ -367,9 +369,10 @@ func Fields(cfg *Config, words ...*syntax.Word) ([]string, error) { fields := make([]string, 0, len(words)) dir := cfg.envGet("PWD") for _, word := range words { - afterBraces := []*syntax.Word{word} - if syntax.SplitBraces(word) { - afterBraces = Braces(word) + word := *word // make a copy, since SplitBraces replaces the Parts slice + afterBraces := []*syntax.Word{&word} + if syntax.SplitBraces(&word) { + afterBraces = Braces(&word) } for _, word2 := range afterBraces { wfields, err := cfg.wordFields(word2.Parts) @@ -540,7 +543,6 @@ func (cfg *Config) wordFields(wps []syntax.WordPart) ([][]fieldPart, error) { } curField = append(curField, fp) case *syntax.DblQuoted: - allowEmpty = true if len(x.Parts) == 1 { pe, _ := x.Parts[0].(*syntax.ParamExp) if elems := cfg.quotedElems(pe); elems != nil { @@ -556,6 +558,7 @@ func (cfg *Config) wordFields(wps []syntax.WordPart) ([][]fieldPart, error) { continue } } + allowEmpty = true wfield, err := cfg.wordField(x.Parts, quoteDouble) if err != nil { return nil, err diff --git a/vendor/mvdan.cc/sh/v3/interp/interp.go b/vendor/mvdan.cc/sh/v3/interp/interp.go index 257ed04f..c3e8e347 100644 --- a/vendor/mvdan.cc/sh/v3/interp/interp.go +++ b/vendor/mvdan.cc/sh/v3/interp/interp.go @@ -745,7 +745,6 @@ func (r *Runner) sub() *Runner { Env: r.Env, Dir: r.Dir, Params: r.Params, - Funcs: r.Funcs, execHandler: r.execHandler, openHandler: r.openHandler, stdin: r.stdin, @@ -766,6 +765,10 @@ func (r *Runner) sub() *Runner { for k, v := range r.cmdVars { r2.cmdVars[k] = v } + r2.Funcs = make(map[string]*syntax.Stmt, len(r.Funcs)) + for k, v := range r.Funcs { + r2.Funcs[k] = v + } r2.dirStack = append(r2.dirBootstrap[:0], r.dirStack...) r2.fillExpandConfig(r.ectx) r2.didReset = true diff --git a/vendor/mvdan.cc/sh/v3/pattern/pattern.go b/vendor/mvdan.cc/sh/v3/pattern/pattern.go index 185f2d80..a36e30c4 100644 --- a/vendor/mvdan.cc/sh/v3/pattern/pattern.go +++ b/vendor/mvdan.cc/sh/v3/pattern/pattern.go @@ -16,8 +16,8 @@ import ( "strings" ) -// TODO: support Mode in the other APIs too - +// Mode can be used to supply a number of options to the package's functions. +// Not all functions change their behavior with all of the options below. type Mode uint const ( @@ -254,13 +254,17 @@ func charClass(s string) (string, error) { // This can be useful to avoid extra work, like TranslatePattern. Note that this // function cannot be used to avoid QuotePattern, as backslashes are quoted by // that function but ignored here. -func HasMeta(pat string) bool { +func HasMeta(pat string, mode Mode) bool { for i := 0; i < len(pat); i++ { switch pat[i] { case '\\': i++ case '*', '?', '[': return true + case '{': + if mode&Braces != 0 { + return true + } } } return false @@ -270,11 +274,16 @@ func HasMeta(pat string) bool { // given text. The returned string is a pattern that matches the literal text. // // For example, QuoteMeta(`foo*bar?`) returns `foo\*bar\?`. -func QuoteMeta(pat string) string { +func QuoteMeta(pat string, mode Mode) string { any := false loop: for _, r := range pat { switch r { + case '{': + if mode&Braces == 0 { + continue + } + fallthrough case '*', '?', '[', '\\': any = true break loop @@ -288,6 +297,10 @@ loop: switch r { case '*', '?', '[', '\\': buf.WriteByte('\\') + case '{': + if mode&Braces != 0 { + buf.WriteByte('\\') + } } buf.WriteRune(r) } diff --git a/vendor/mvdan.cc/sh/v3/shell/source.go b/vendor/mvdan.cc/sh/v3/shell/source.go deleted file mode 100644 index 13261ffd..00000000 --- a/vendor/mvdan.cc/sh/v3/shell/source.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) 2018, Daniel Martí -// See LICENSE for licensing information - -package shell - -import ( - "context" - "fmt" - "os" - - "mvdan.cc/sh/v3/expand" - "mvdan.cc/sh/v3/interp" - "mvdan.cc/sh/v3/syntax" -) - -// SourceFile sources a shell file from disk and returns the variables -// declared in it. It is a convenience function that uses a default shell -// parser, parses a file from disk, and calls SourceNode. -// -// This function should be used with caution, as it can interpret arbitrary -// code. Untrusted shell programs shoudn't be sourced outside of a sandbox -// environment. -func SourceFile(ctx context.Context, path string) (map[string]expand.Variable, error) { - f, err := os.Open(path) - if err != nil { - return nil, fmt.Errorf("could not open: %v", err) - } - defer f.Close() - file, err := syntax.NewParser().Parse(f, path) - if err != nil { - return nil, fmt.Errorf("could not parse: %v", err) - } - return SourceNode(ctx, file) -} - -// SourceNode sources a shell program from a node and returns the -// variables declared in it. It accepts the same set of node types that -// interp/Runner.Run does. -// -// This function should be used with caution, as it can interpret arbitrary -// code. Untrusted shell programs shoudn't be sourced outside of a sandbox -// environment. -func SourceNode(ctx context.Context, node syntax.Node) (map[string]expand.Variable, error) { - r, _ := interp.New() - if err := r.Run(ctx, node); err != nil { - return nil, fmt.Errorf("could not run: %v", err) - } - // delete the internal shell vars that the user is not - // interested in - delete(r.Vars, "PWD") - delete(r.Vars, "UID") - delete(r.Vars, "HOME") - delete(r.Vars, "PATH") - delete(r.Vars, "IFS") - delete(r.Vars, "OPTIND") - return r.Vars, nil -} diff --git a/vendor/mvdan.cc/sh/v3/syntax/braces.go b/vendor/mvdan.cc/sh/v3/syntax/braces.go index 0baa43a0..dca854fd 100644 --- a/vendor/mvdan.cc/sh/v3/syntax/braces.go +++ b/vendor/mvdan.cc/sh/v3/syntax/braces.go @@ -129,7 +129,6 @@ func SplitBraces(word *Word) bool { broken = true } if !broken { - br.Chars = chars[0] acc.Parts = append(acc.Parts, br) break } diff --git a/vendor/mvdan.cc/sh/v3/syntax/lexer.go b/vendor/mvdan.cc/sh/v3/syntax/lexer.go index ac144cc2..b3daba6d 100644 --- a/vendor/mvdan.cc/sh/v3/syntax/lexer.go +++ b/vendor/mvdan.cc/sh/v3/syntax/lexer.go @@ -256,6 +256,7 @@ skipSpace: for r != '\n' && r != utf8.RuneSelf { if r == escNewl { p.litBs = append(p.litBs, '\\', '\n') + break } r = p.rune() } @@ -316,6 +317,7 @@ skipSpace: } else { p.tok = rightParen p.quote = noState + p.rune() // we are tokenizing manually } default: // including '(', '|' p.advanceLitRe(r) diff --git a/vendor/mvdan.cc/sh/v3/syntax/nodes.go b/vendor/mvdan.cc/sh/v3/syntax/nodes.go index e3d08b24..b8fa71bf 100644 --- a/vendor/mvdan.cc/sh/v3/syntax/nodes.go +++ b/vendor/mvdan.cc/sh/v3/syntax/nodes.go @@ -409,9 +409,9 @@ type Word struct { func (w *Word) Pos() Pos { return w.Parts[0].Pos() } func (w *Word) End() Pos { return w.Parts[len(w.Parts)-1].End() } -// Lit returns the word as a literal value, if the word consists of *syntax.Lit -// nodes only. An empty string is returned otherwise. Words with multiple -// literals, which can appear in some edge cases, are handled properly. +// Lit returns the word as a literal value, if the word consists of *Lit nodes +// only. An empty string is returned otherwise. Words with multiple literals, +// which can appear in some edge cases, are handled properly. // // For example, the word "foo" will return "foo", but the word "foo${bar}" will // return "". @@ -859,12 +859,11 @@ type LetClause struct { func (l *LetClause) Pos() Pos { return l.Let } func (l *LetClause) End() Pos { return l.Exprs[len(l.Exprs)-1].End() } -// BraceExp represents a Bash brace expression, such as "{x,y}" or "{1..10}". +// BraceExp represents a Bash brace expression, such as "{a,f}" or "{1..10}". // // This node will only appear as a result of SplitBraces. type BraceExp struct { Sequence bool // {x..y[..incr]} instead of {x,y[,...]} - Chars bool // sequence is of chars, not numbers (TODO: remove) Elems []*Word } diff --git a/vendor/mvdan.cc/sh/v3/syntax/parser.go b/vendor/mvdan.cc/sh/v3/syntax/parser.go index bce05999..e445b0ad 100644 --- a/vendor/mvdan.cc/sh/v3/syntax/parser.go +++ b/vendor/mvdan.cc/sh/v3/syntax/parser.go @@ -14,7 +14,7 @@ import ( // ParserOption is a function which can be passed to NewParser // to alter its behaviour. To apply option to existing Parser -// call it directly, for example syntax.KeepComments(true)(parser). +// call it directly, for example KeepComments(true)(parser). type ParserOption func(*Parser) // KeepComments makes the parser parse comments and attach them to @@ -524,9 +524,13 @@ func (p *Parser) unquotedWordPart(buf *bytes.Buffer, wp WordPart, quotes bool) ( } func (p *Parser) doHeredocs() { + hdocs := p.heredocs[p.buriedHdocs:] + if len(hdocs) == 0 { + // Nothing do do; don't even issue a read. + return + } p.rune() // consume '\n', since we know p.tok == _Newl old := p.quote - hdocs := p.heredocs[p.buriedHdocs:] p.heredocs = p.heredocs[:p.buriedHdocs] for i, r := range hdocs { if p.err != nil { @@ -840,7 +844,7 @@ func (p *Parser) invalidStmtStart() { } func (p *Parser) getWord() *Word { - if parts := p.wordParts(); len(parts) > 0 { + if parts := p.wordParts(); len(parts) > 0 && p.err == nil { return p.word(parts) } return nil @@ -1546,13 +1550,15 @@ func (p *Parser) hasValidIdent() bool { } if end := p.eqlOffs; end > 0 { if p.val[end-1] == '+' && p.lang != LangPOSIX { - end-- + end-- // a+=x } if ValidName(p.val[:end]) { return true } + } else if !ValidName(p.val) { + return false // *[i]=x } - return p.r == '[' + return p.r == '[' // a[i]=x } func (p *Parser) getAssign(needEqual bool) *Assign { diff --git a/vendor/mvdan.cc/sh/v3/syntax/printer.go b/vendor/mvdan.cc/sh/v3/syntax/printer.go index 2bb80e47..8ab1bdcd 100644 --- a/vendor/mvdan.cc/sh/v3/syntax/printer.go +++ b/vendor/mvdan.cc/sh/v3/syntax/printer.go @@ -15,7 +15,7 @@ import ( // PrinterOption is a function which can be passed to NewPrinter // to alter its behaviour. To apply option to existing Printer -// call it directly, for example syntax.KeepPadding(true)(printer). +// call it directly, for example KeepPadding(true)(printer). type PrinterOption func(*Printer) // Indent sets the number of spaces used for indentation. If set to 0, @@ -414,13 +414,13 @@ func (p *Printer) flushHeredocs() { bufWriter: &extra, line: r.Hdoc.Pos().Line(), } - p.tabsPrinter.word(r.Hdoc) + p.tabsPrinter.wordParts(r.Hdoc.Parts, true) p.indent() } else { p.indent() } } else if r.Hdoc != nil { - p.word(r.Hdoc) + p.wordParts(r.Hdoc.Parts, true) } p.unquotedWord(r.Word) if r.Hdoc != nil { @@ -478,6 +478,11 @@ func (p *Printer) semiRsrv(s string, pos Pos) { func (p *Printer) flushComments() { for i, c := range p.pendingComments { + if i == 0 { + // Flush any pending heredocs first. Otherwise, the + // comments would become part of a heredoc body. + p.flushHeredocs() + } p.firstLine = false // We can't call any of the newline methods, as they call this // function and we'd recurse forever.