1
0
mirror of https://github.com/go-task/task.git synced 2025-03-05 15:05:42 +02:00

Merge branch 'master' into master

This commit is contained in:
Tobias Salzmann 2018-08-01 10:47:25 +02:00 committed by GitHub
commit 90a5f17f58
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
56 changed files with 886 additions and 299 deletions

View File

@ -1,9 +1,6 @@
* Bug reports and feature requests are welcome in [the issues][issues]
* For questions and discussion there's the [Slack room][slack] ([invititation here][slackinvite])
* Pull Requests are welcome. For more complex changes and features it's
recommended to open an issue with the feature request first
* Documentation contributions are as important as code contributions
[issues]: https://github.com/go-task/task/issues
[slack]: https://gophers.slack.com/messages/task
[slackinvite]: https://invite.slack.golangbridge.org/

View File

@ -1,7 +1,4 @@
<!--
For questions and general talk there's the Slack room: https://gophers.slack.com/messages/task
Invite to the Slack is available in this link: https://invite.slack.golangbridge.org/
If relevant, include the following information:
- Task version
- OS

83
Gopkg.lock generated
View File

@ -3,129 +3,186 @@
[[projects]]
branch = "master"
digest = "1:f3960e064201714a3507bf96183be246b3d941568af01dc5cff2a388ac4c7515"
name = "github.com/Masterminds/semver"
packages = ["."]
revision = "3c560837130448941620d7694991d3ec440aefc0"
pruneopts = "NUT"
revision = "4ca3c04fd4fe2a472df0d7121b1b9462f2214c43"
[[projects]]
branch = "master"
digest = "1:43f9a530dfe36fb355b05fbc7a5712f8149a7f6bdfd131bc0ccc634e25c4dd1e"
name = "github.com/Masterminds/sprig"
packages = ["."]
pruneopts = "NUT"
revision = "6b2a58267f6a8b1dc8e2eb5519b984008fa85e8c"
version = "v2.15.0"
[[projects]]
digest = "1:975108e8d4f5dab096fc991326e96a5716ee8d02e5e7386bb4796171afc4ab9a"
name = "github.com/aokoli/goutils"
packages = ["."]
pruneopts = "NUT"
revision = "3391d3790d23d03408670993e957e8f408993c34"
version = "v1.0.1"
[[projects]]
digest = "1:a2c1d0e43bd3baaa071d1b9ed72c27d78169b2b269f71c105ac4ba34b1be4a39"
name = "github.com/davecgh/go-spew"
packages = ["spew"]
pruneopts = "NUT"
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
version = "v1.1.0"
[[projects]]
digest = "1:1bb197a3b5db4e06e00b7560f8e89836c486627f2a0338332ed37daa003d259e"
name = "github.com/google/uuid"
packages = ["."]
pruneopts = "NUT"
revision = "064e2069ce9c359c118179501254f67d7d37ba24"
version = "0.2"
[[projects]]
digest = "1:f5db19d350bd0b542d17f7e7cf4e7068bac416c08adb6a129b3c6d1db8211051"
name = "github.com/huandu/xstrings"
packages = ["."]
pruneopts = "NUT"
revision = "2bf18b218c51864a87384c06996e40ff9dcff8e1"
version = "v1.0.0"
[[projects]]
digest = "1:65300ccc4bcb38b107b868155c303312978981e56bca707c81efec57575b5e06"
name = "github.com/imdario/mergo"
packages = ["."]
pruneopts = "NUT"
revision = "9316a62528ac99aaecb4e47eadd6dc8aa6533d58"
version = "v0.3.5"
[[projects]]
branch = "master"
digest = "1:fdf499904ff0a8b5e05a32d98a1e65ddaff6b358640dbd8696ce8bb4e8d2d246"
name = "github.com/mattn/go-zglob"
packages = [
".",
"fastwalk"
"fastwalk",
]
revision = "49693fbb3fe3c3a75fc4e4d6fb1d7cedcbdeb385"
pruneopts = "NUT"
revision = "c436403c742d0b6d8fc37e69eadf33e024fe74fa"
[[projects]]
branch = "master"
digest = "1:9db29b604bd78452d167abed82386ddd2f93973df3841896fb6ab8aff936f1d6"
name = "github.com/mitchellh/go-homedir"
packages = ["."]
pruneopts = "NUT"
revision = "3864e76763d94a6df2f9960b16a20a33da9f9a66"
[[projects]]
digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe"
name = "github.com/pmezard/go-difflib"
packages = ["difflib"]
pruneopts = "NUT"
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
version = "v1.0.0"
[[projects]]
branch = "master"
digest = "1:5089085e1b27a57be4fb7acf32bfa71fb6236dc0e8371165651c9e15285a9ce0"
name = "github.com/radovskyb/watcher"
packages = ["."]
pruneopts = "NUT"
revision = "0d9d32686dbf6395752c9b209398a59e302a7f1e"
[[projects]]
branch = "master"
digest = "1:8c05fbdaac9713aed2a89d171a8b4e98a563f6c84e48352d0bcb10b1a5122488"
name = "github.com/spf13/pflag"
packages = ["."]
pruneopts = "NUT"
revision = "3ebe029320b2676d667ae88da602a5f854788a8a"
[[projects]]
digest = "1:bacb8b590716ab7c33f2277240972c9582d389593ee8d66fc10074e0508b8126"
name = "github.com/stretchr/testify"
packages = ["assert"]
pruneopts = "NUT"
revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686"
version = "v1.2.2"
[[projects]]
branch = "master"
digest = "1:17ff32de9f3bf39f76d339ddffcd380140721b112bea96837a86d4dc26c2c633"
name = "golang.org/x/crypto"
packages = [
"pbkdf2",
"scrypt",
"ssh/terminal"
"ssh/terminal",
]
revision = "a49355c7e3f8fe157a85be2f77e6e269a0f89602"
pruneopts = "NUT"
revision = "a2144134853fc9a27a7b1e3eb4f19f1a76df13c9"
[[projects]]
branch = "master"
digest = "1:76ee51c3f468493aff39dbacc401e8831fbb765104cbf613b89bef01cf4bad70"
name = "golang.org/x/net"
packages = ["context"]
revision = "afe8f62b1d6bbd81f31868121a50b06d8188e1f9"
pruneopts = "NUT"
revision = "a680a1efc54dd51c040b3b5ce4939ea3cf2ea0d1"
[[projects]]
branch = "master"
digest = "1:39ebcc2b11457b703ae9ee2e8cca0f68df21969c6102cb3b705f76cca0ea0239"
name = "golang.org/x/sync"
packages = ["errgroup"]
pruneopts = "NUT"
revision = "1d60e4601c6fd243af51cc01ddf169918a5407ca"
[[projects]]
branch = "master"
digest = "1:64107e3c8f52f341891a565d117bf263ed396fe0c16bad19634b25be25debfaa"
name = "golang.org/x/sys"
packages = [
"unix",
"windows"
"windows",
]
revision = "63fc586f45fe72d95d5240a5d5eb95e6503907d3"
pruneopts = "NUT"
revision = "ac767d655b305d4e9612f5f6e33120b9176c4ad4"
[[projects]]
branch = "v2"
digest = "1:7c95b35057a0ff2e19f707173cc1a947fa43a6eb5c4d300d196ece0334046082"
name = "gopkg.in/yaml.v2"
packages = ["."]
pruneopts = "NUT"
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
version = "v2.2.1"
[[projects]]
branch = "master"
digest = "1:01be9a02fb8bcae03383a7422d0bd48813fd49834a50be5ef933e430604b3423"
name = "mvdan.cc/sh"
packages = [
"interp",
"shell",
"syntax"
"syntax",
]
revision = "ca7561fd34910fd8575a3830d3cded291c0ce8b2"
pruneopts = "NUT"
revision = "54e5852f101469e5ff9b03902ce0d4ff2ef09809"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "600bd482208fdedec60141bfaffe55eb403df077944bfdf5c007a33132c8ab5a"
input-imports = [
"github.com/Masterminds/semver",
"github.com/Masterminds/sprig",
"github.com/mattn/go-zglob",
"github.com/mitchellh/go-homedir",
"github.com/radovskyb/watcher",
"github.com/spf13/pflag",
"github.com/stretchr/testify/assert",
"golang.org/x/sync/errgroup",
"gopkg.in/yaml.v2",
"mvdan.cc/sh/interp",
"mvdan.cc/sh/shell",
"mvdan.cc/sh/syntax",
]
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -7,10 +7,6 @@
branch = "master"
name = "github.com/Masterminds/sprig"
[[constraint]]
branch = "master"
name = "github.com/imdario/mergo"
[[constraint]]
branch = "master"
name = "github.com/mattn/go-zglob"

View File

@ -1,4 +1,3 @@
[![Join Slack room](https://img.shields.io/badge/%23task-chat%20room-blue.svg)](https://gophers.slack.com/messages/task)
[![Build Status](https://travis-ci.org/go-task/task.svg?branch=master)](https://travis-ci.org/go-task/task)
# Task - A task runner / simpler Make alternative written in Go
@ -127,7 +126,7 @@ tasks:
### OS specific task
If you add a `Taskfile_{{GOOS}}.yml` you can override or amend your taskfile
If you add a `Taskfile_{{GOOS}}.yml` you can override or amend your Taskfile
based on the operating system.
Example:
@ -146,13 +145,18 @@ tasks:
Taskfile_linux.yml:
```yml
version: '2'
tasks:
build:
cmds:
- echo "linux"
```
Will print out `linux` and not default.
Will print out `linux` and not `default`.
Keep in mind that the version of the files should match. Also, when redefining
a task the whole task is replaced, properties of the task are not merged.
It's also possible to have an OS specific `Taskvars.yml` file, like
`Taskvars_windows.yml`, `Taskfile_linux.yml`, or `Taskvars_darwin.yml`. See the
@ -325,7 +329,6 @@ If you prefer this check to be made by the content of the files, instead of
its timestamp, just set the `method` property to `checksum`.
You will probably want to ignore the `.task` folder in your `.gitignore` file
(It's there that Task stores the last checksum).
This feature is still experimental and can change until it's stable.
```yml
version: '2'

View File

@ -5,24 +5,13 @@ vars:
sh: git log -n 1 --format=%h
GO_PACKAGES:
.
./cmd/task
./internal/args
./internal/compiler
./internal/compiler/v1
./internal/compiler/v2
./internal/execext
./internal/logger
./internal/osext
./internal/output
./internal/status
./internal/taskfile
./internal/taskfile/version
./internal/templater
sh: go list ./...
tasks:
# compiles current source code and make "task" executable available on
# $GOPATH/bin/task{.exe}
default:
cmds:
- task: test
install:
desc: Installs Task
cmds:
@ -35,8 +24,6 @@ tasks:
cmds:
- task: go-get
vars: {REPO: github.com/golang/lint/golint}
- task: go-get
vars: {REPO: github.com/asticode/go-astitodo/astitodo}
- task: go-get
vars: {REPO: github.com/golang/dep/cmd/dep}
- task: go-get
@ -58,14 +45,14 @@ tasks:
lint:
desc: Runs golint
cmds:
- golint {{.GO_PACKAGES}}
- golint {{catLines .GO_PACKAGES}}
silent: true
test:
desc: Runs test suite
deps: [install]
cmds:
- go test {{.GO_PACKAGES}}
- go test {{catLines .GO_PACKAGES}}
test-release:
desc: Tests release process without publishing
@ -77,12 +64,6 @@ tasks:
cmds:
- godownloader --repo go-task/task -o install-task.sh
todo:
desc: Prints TODO comments present in the code
cmds:
- astitodo {{.GO_PACKAGES}}
silent: true
ci:
cmds:
- task: go-get
@ -93,3 +74,8 @@ tasks:
go-get:
cmds:
- go get -u {{.REPO}}
packages:
cmds:
- echo '{{.GO_PACKAGES}}'
silent: true

View File

@ -1,22 +0,0 @@
package osext
import (
"os"
"github.com/mitchellh/go-homedir"
)
// Expand is an improved version of os.ExpandEnv,
// that not only expand enrionment variable ($GOPATH/src/github.com/...)
// but also expands "~" as the home directory.
func Expand(s string) (string, error) {
s = os.ExpandEnv(s)
var err error
s, err = homedir.Expand(s)
if err != nil {
return "", err
}
return s, nil
}

View File

@ -4,9 +4,8 @@ import (
"path/filepath"
"sort"
"github.com/go-task/task/internal/osext"
"github.com/mattn/go-zglob"
"mvdan.cc/sh/shell"
)
func glob(dir string, globs []string) (files []string, err error) {
@ -14,7 +13,7 @@ func glob(dir string, globs []string) (files []string, err error) {
if !filepath.IsAbs(g) {
g = filepath.Join(dir, g)
}
g, err = osext.Expand(g)
g, err = shell.Expand(g, nil)
if err != nil {
return nil, err
}

View File

@ -0,0 +1,27 @@
package taskfile
import (
"fmt"
)
// Merge merges the second Taskfile into the first
func Merge(t1, t2 *Taskfile) error {
if t1.Version != t2.Version {
return fmt.Errorf(`Taskfiles versions should match. First is "%s" but second is "%s"`, t1.Version, t2.Version)
}
if t2.Expansions != 0 && t2.Expansions != 2 {
t1.Expansions = t2.Expansions
}
if t2.Output != "" {
t1.Output = t2.Output
}
for k, v := range t2.Vars {
t1.Vars[k] = v
}
for k, v := range t2.Tasks {
t1.Tasks[k] = v
}
return nil
}

View File

@ -0,0 +1,47 @@
package read
import (
"fmt"
"os"
"path/filepath"
"runtime"
"github.com/go-task/task/internal/taskfile"
"gopkg.in/yaml.v2"
)
// Taskfile reads a Taskfile for a given directory
func Taskfile(dir string) (*taskfile.Taskfile, error) {
path := filepath.Join(dir, "Taskfile.yml")
t, err := readTaskfile(path)
if err != nil {
return nil, err
}
path = filepath.Join(dir, fmt.Sprintf("Taskfile_%s.yml", runtime.GOOS))
if _, err = os.Stat(path); err == nil {
osTaskfile, err := readTaskfile(path)
if err != nil {
return nil, err
}
if err = taskfile.Merge(t, osTaskfile); err != nil {
return nil, err
}
}
for name, task := range t.Tasks {
task.Task = name
}
return t, nil
}
func readTaskfile(file string) (*taskfile.Taskfile, error) {
f, err := os.Open(file)
if err != nil {
return nil, err
}
var t taskfile.Taskfile
return &t, yaml.NewDecoder(f).Decode(&t)
}

View File

@ -0,0 +1,52 @@
package read
import (
"fmt"
"os"
"path/filepath"
"runtime"
"github.com/go-task/task/internal/taskfile"
"gopkg.in/yaml.v2"
)
// Taskvars reads a Taskvars for a given directory
func Taskvars(dir string) (taskfile.Vars, error) {
vars := make(taskfile.Vars)
path := filepath.Join(dir, "Taskvars.yml")
if _, err := os.Stat(path); err == nil {
vars, err = readTaskvars(path)
if err != nil {
return nil, err
}
}
path = filepath.Join(dir, fmt.Sprintf("Taskvars_%s.yml", runtime.GOOS))
if _, err := os.Stat(path); err == nil {
osVars, err := readTaskvars(path)
if err != nil {
return nil, err
}
if vars == nil {
vars = osVars
} else {
for k, v := range osVars {
vars[k] = v
}
}
}
return vars, nil
}
func readTaskvars(file string) (taskfile.Vars, error) {
f, err := os.Open(file)
if err != nil {
return nil, err
}
var vars taskfile.Vars
return vars, yaml.NewDecoder(f).Decode(&vars)
}

11
task.go
View File

@ -14,6 +14,7 @@ import (
"github.com/go-task/task/internal/logger"
"github.com/go-task/task/internal/output"
"github.com/go-task/task/internal/taskfile"
"github.com/go-task/task/internal/taskfile/read"
"github.com/go-task/task/internal/taskfile/version"
"github.com/Masterminds/semver"
@ -21,8 +22,6 @@ import (
)
const (
// TaskFilePath is the default Taskfile
TaskFilePath = "Taskfile"
// MaximumTaskCall is the max number of times a task can be called.
// This exists to prevent infinite loops on cyclic dependencies
MaximumTaskCall = 100
@ -77,7 +76,13 @@ func (e *Executor) Run(calls ...taskfile.Call) error {
// Setup setups Executor's internal state
func (e *Executor) Setup() error {
if err := e.readTaskfile(); err != nil {
var err error
e.Taskfile, err = read.Taskfile(e.Dir)
if err != nil {
return err
}
e.taskvars, err = read.Taskvars(e.Dir)
if err != nil {
return err
}

View File

@ -12,6 +12,7 @@ import (
"github.com/go-task/task"
"github.com/go-task/task/internal/taskfile"
"github.com/mitchellh/go-homedir"
"github.com/stretchr/testify/assert"
)
@ -435,3 +436,22 @@ func TestTaskIgnoreErrors(t *testing.T) {
assert.Error(t, e.Run(taskfile.Call{Task: "cmd-should-fail"}))
})
}
func TestExpand(t *testing.T) {
const dir = "testdata/expand"
home, err := homedir.Dir()
if err != nil {
t.Errorf("Couldn't get $HOME: %v", err)
}
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Stdout: &buff,
Stderr: &buff,
}
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(taskfile.Call{Task: "pwd"}))
assert.Equal(t, home, strings.TrimSpace(buff.String()))
}

View File

@ -1,74 +0,0 @@
package task
import (
"fmt"
"io/ioutil"
"path/filepath"
"runtime"
"github.com/go-task/task/internal/taskfile"
"github.com/imdario/mergo"
"gopkg.in/yaml.v2"
)
// readTaskfile parses Taskfile from the disk
func (e *Executor) readTaskfile() error {
path := filepath.Join(e.Dir, TaskFilePath)
var err error
e.Taskfile, err = e.readTaskfileData(path)
if err != nil {
return err
}
osTasks, err := e.readTaskfileData(fmt.Sprintf("%s_%s", path, runtime.GOOS))
if err != nil {
switch err.(type) {
case taskFileNotFound:
default:
return err
}
} else {
if err := mergo.MapWithOverwrite(&e.Taskfile.Tasks, osTasks.Tasks); err != nil {
return err
}
}
for name, task := range e.Taskfile.Tasks {
task.Task = name
}
return e.readTaskvars()
}
func (e *Executor) readTaskfileData(path string) (*taskfile.Taskfile, error) {
if b, err := ioutil.ReadFile(path + ".yml"); err == nil {
var taskfile taskfile.Taskfile
return &taskfile, yaml.Unmarshal(b, &taskfile)
}
return nil, taskFileNotFound{path}
}
func (e *Executor) readTaskvars() error {
var (
file = filepath.Join(e.Dir, TaskvarsFilePath)
osSpecificFile = fmt.Sprintf("%s_%s", file, runtime.GOOS)
)
if b, err := ioutil.ReadFile(file + ".yml"); err == nil {
if err := yaml.Unmarshal(b, &e.taskvars); err != nil {
return err
}
}
if b, err := ioutil.ReadFile(osSpecificFile + ".yml"); err == nil {
osTaskvars := make(taskfile.Vars, 10)
if err := yaml.Unmarshal(b, &osTaskvars); err != nil {
return err
}
for k, v := range osTaskvars {
e.taskvars[k] = v
}
}
return nil
}

8
testdata/expand/Taskfile.yml vendored Normal file
View File

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

View File

@ -3,14 +3,10 @@ package task
import (
"path/filepath"
"github.com/go-task/task/internal/osext"
"github.com/go-task/task/internal/taskfile"
"github.com/go-task/task/internal/templater"
)
var (
// TaskvarsFilePath file containing additional variables.
TaskvarsFilePath = "Taskvars"
"mvdan.cc/sh/shell"
)
// CompiledTask returns a copy of a task, but replacing variables in almost all
@ -40,7 +36,7 @@ func (e *Executor) CompiledTask(call taskfile.Call) (*taskfile.Task, error) {
Method: r.Replace(origTask.Method),
Prefix: r.Replace(origTask.Prefix),
}
new.Dir, err = osext.Expand(new.Dir)
new.Dir, err = shell.Expand(new.Dir, nil)
if err != nil {
return nil, err
}

View File

@ -47,7 +47,7 @@ parts of the package.
// Handle constraint not being parseable.
}
v, _ := semver.NewVersion("1.3")
v, err := semver.NewVersion("1.3")
if err != nil {
// Handle version not being parseable.
}

View File

@ -106,7 +106,7 @@ func MustParse(v string) *Version {
// Note, if the original version contained a leading v this version will not.
// See the Original() method to retrieve the original value. Semantic Versions
// don't contain a leading v per the spec. Instead it's optional on
// impelementation.
// implementation.
func (v *Version) String() string {
var buf bytes.Buffer

View File

@ -19,6 +19,7 @@ import (
"os"
"path/filepath"
"runtime"
"sync"
)
// TraverseLink is a sentinel error for fastWalk, similar to filepath.SkipDir.
@ -67,11 +68,18 @@ func FastWalk(root string, walkFn func(path string, typ os.FileMode) error) erro
// buffered for correctness & not leaking goroutines:
resc: make(chan error, numWorkers),
}
defer close(w.donec)
// TODO(bradfitz): start the workers as needed? maybe not worth it.
var wg sync.WaitGroup
for i := 0; i < numWorkers; i++ {
go w.doWork()
wg.Add(1)
go w.doWork(&wg)
}
// Ensure we wait for goroutines we started to finish before we return.
defer wg.Wait()
defer close(w.donec)
todo := []walkItem{{dir: root}}
out := 0
for {
@ -113,10 +121,11 @@ func FastWalk(root string, walkFn func(path string, typ os.FileMode) error) erro
// doWork reads directories as instructed (via workc) and runs the
// user's callback function.
func (w *walker) doWork() {
func (w *walker) doWork(wg *sync.WaitGroup) {
for {
select {
case <-w.donec:
wg.Done()
return
case it := <-w.workc:
w.resc <- w.walk(it.dir, !it.callbackDone)

21
vendor/github.com/mitchellh/go-homedir/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2013 Mitchell Hashimoto
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.

155
vendor/github.com/mitchellh/go-homedir/homedir.go generated vendored Normal file
View File

@ -0,0 +1,155 @@
package homedir
import (
"bytes"
"errors"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"sync"
)
// DisableCache will disable caching of the home directory. Caching is enabled
// by default.
var DisableCache bool
var homedirCache string
var cacheLock sync.RWMutex
// Dir returns the home directory for the executing user.
//
// This uses an OS-specific method for discovering the home directory.
// An error is returned if a home directory cannot be detected.
func Dir() (string, error) {
if !DisableCache {
cacheLock.RLock()
cached := homedirCache
cacheLock.RUnlock()
if cached != "" {
return cached, nil
}
}
cacheLock.Lock()
defer cacheLock.Unlock()
var result string
var err error
if runtime.GOOS == "windows" {
result, err = dirWindows()
} else {
// Unix-like system, so just assume Unix
result, err = dirUnix()
}
if err != nil {
return "", err
}
homedirCache = result
return result, nil
}
// Expand expands the path to include the home directory if the path
// is prefixed with `~`. If it isn't prefixed with `~`, the path is
// returned as-is.
func Expand(path string) (string, error) {
if len(path) == 0 {
return path, nil
}
if path[0] != '~' {
return path, nil
}
if len(path) > 1 && path[1] != '/' && path[1] != '\\' {
return "", errors.New("cannot expand user-specific home dir")
}
dir, err := Dir()
if err != nil {
return "", err
}
return filepath.Join(dir, path[1:]), nil
}
func dirUnix() (string, error) {
homeEnv := "HOME"
if runtime.GOOS == "plan9" {
// On plan9, env vars are lowercase.
homeEnv = "home"
}
// First prefer the HOME environmental variable
if home := os.Getenv(homeEnv); home != "" {
return home, nil
}
var stdout bytes.Buffer
// If that fails, try OS specific commands
if runtime.GOOS == "darwin" {
cmd := exec.Command("sh", "-c", `dscl -q . -read /Users/"$(whoami)" NFSHomeDirectory | sed 's/^[^ ]*: //'`)
cmd.Stdout = &stdout
if err := cmd.Run(); err == nil {
result := strings.TrimSpace(stdout.String())
if result != "" {
return result, nil
}
}
} else {
cmd := exec.Command("getent", "passwd", strconv.Itoa(os.Getuid()))
cmd.Stdout = &stdout
if err := cmd.Run(); err != nil {
// If the error is ErrNotFound, we ignore it. Otherwise, return it.
if err != exec.ErrNotFound {
return "", err
}
} else {
if passwd := strings.TrimSpace(stdout.String()); passwd != "" {
// username:password:uid:gid:gecos:home:shell
passwdParts := strings.SplitN(passwd, ":", 7)
if len(passwdParts) > 5 {
return passwdParts[5], nil
}
}
}
}
// If all else fails, try the shell
stdout.Reset()
cmd := exec.Command("sh", "-c", "cd && pwd")
cmd.Stdout = &stdout
if err := cmd.Run(); err != nil {
return "", err
}
result := strings.TrimSpace(stdout.String())
if result == "" {
return "", errors.New("blank output when reading home directory")
}
return result, nil
}
func dirWindows() (string, error) {
// First prefer the HOME environmental variable
if home := os.Getenv("HOME"); home != "" {
return home, nil
}
drive := os.Getenv("HOMEDRIVE")
path := os.Getenv("HOMEPATH")
home := drive + path
if drive == "" || path == "" {
home = os.Getenv("USERPROFILE")
}
if home == "" {
return "", errors.New("HOMEDRIVE, HOMEPATH, and USERPROFILE are blank")
}
return home, nil
}

View File

@ -14,7 +14,11 @@ var fcntl64Syscall uintptr = SYS_FCNTL
// FcntlInt performs a fcntl syscall on fd with the provided command and argument.
func FcntlInt(fd uintptr, cmd, arg int) (int, error) {
valptr, _, err := Syscall(fcntl64Syscall, fd, uintptr(cmd), uintptr(arg))
valptr, _, errno := Syscall(fcntl64Syscall, fd, uintptr(cmd), uintptr(arg))
var err error
if errno != 0 {
err = errno
}
return int(valptr), err
}

View File

@ -206,7 +206,7 @@ func (sa *SockaddrDatalink) sockaddr() (unsafe.Pointer, _Socklen, error) {
return unsafe.Pointer(&sa.raw), SizeofSockaddrDatalink, nil
}
func anyToSockaddr(rsa *RawSockaddrAny) (Sockaddr, error) {
func anyToSockaddr(fd int, rsa *RawSockaddrAny) (Sockaddr, error) {
switch rsa.Addr.Family {
case AF_LINK:
pp := (*RawSockaddrDatalink)(unsafe.Pointer(rsa))
@ -286,7 +286,7 @@ func Accept(fd int) (nfd int, sa Sockaddr, err error) {
Close(nfd)
return 0, nil, ECONNABORTED
}
sa, err = anyToSockaddr(&rsa)
sa, err = anyToSockaddr(fd, &rsa)
if err != nil {
Close(nfd)
nfd = 0
@ -306,7 +306,7 @@ func Getsockname(fd int) (sa Sockaddr, err error) {
rsa.Addr.Family = AF_UNIX
rsa.Addr.Len = SizeofSockaddrUnix
}
return anyToSockaddr(&rsa)
return anyToSockaddr(fd, &rsa)
}
//sysnb socketpair(domain int, typ int, proto int, fd *[2]int32) (err error)
@ -356,7 +356,7 @@ func Recvmsg(fd int, p, oob []byte, flags int) (n, oobn int, recvflags int, from
recvflags = int(msg.Flags)
// source address is only specified if the socket is unconnected
if rsa.Addr.Family != AF_UNSPEC {
from, err = anyToSockaddr(&rsa)
from, err = anyToSockaddr(fd, &rsa)
}
return
}

View File

@ -87,7 +87,7 @@ func Accept4(fd, flags int) (nfd int, sa Sockaddr, err error) {
if len > SizeofSockaddrAny {
panic("RawSockaddrAny too small")
}
sa, err = anyToSockaddr(&rsa)
sa, err = anyToSockaddr(fd, &rsa)
if err != nil {
Close(nfd)
nfd = 0

View File

@ -89,7 +89,7 @@ func Accept4(fd, flags int) (nfd int, sa Sockaddr, err error) {
if len > SizeofSockaddrAny {
panic("RawSockaddrAny too small")
}
sa, err = anyToSockaddr(&rsa)
sa, err = anyToSockaddr(fd, &rsa)
if err != nil {
Close(nfd)
nfd = 0

View File

@ -489,6 +489,47 @@ func (sa *SockaddrL2) sockaddr() (unsafe.Pointer, _Socklen, error) {
return unsafe.Pointer(&sa.raw), SizeofSockaddrL2, nil
}
// SockaddrRFCOMM implements the Sockaddr interface for AF_BLUETOOTH type sockets
// using the RFCOMM protocol.
//
// Server example:
//
// fd, _ := Socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM)
// _ = unix.Bind(fd, &unix.SockaddrRFCOMM{
// Channel: 1,
// Addr: [6]uint8{0, 0, 0, 0, 0, 0}, // BDADDR_ANY or 00:00:00:00:00:00
// })
// _ = Listen(fd, 1)
// nfd, sa, _ := Accept(fd)
// fmt.Printf("conn addr=%v fd=%d", sa.(*unix.SockaddrRFCOMM).Addr, nfd)
// Read(nfd, buf)
//
// Client example:
//
// fd, _ := Socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM)
// _ = Connect(fd, &SockaddrRFCOMM{
// Channel: 1,
// Addr: [6]byte{0x11, 0x22, 0x33, 0xaa, 0xbb, 0xcc}, // CC:BB:AA:33:22:11
// })
// Write(fd, []byte(`hello`))
type SockaddrRFCOMM struct {
// Addr represents a bluetooth address, byte ordering is little-endian.
Addr [6]uint8
// Channel is a designated bluetooth channel, only 1-30 are available for use.
// Since Linux 2.6.7 and further zero value is the first available channel.
Channel uint8
raw RawSockaddrRFCOMM
}
func (sa *SockaddrRFCOMM) sockaddr() (unsafe.Pointer, _Socklen, error) {
sa.raw.Family = AF_BLUETOOTH
sa.raw.Channel = sa.Channel
sa.raw.Bdaddr = sa.Addr
return unsafe.Pointer(&sa.raw), SizeofSockaddrRFCOMM, nil
}
// SockaddrCAN implements the Sockaddr interface for AF_CAN type sockets.
// The RxID and TxID fields are used for transport protocol addressing in
// (CAN_TP16, CAN_TP20, CAN_MCNET, and CAN_ISOTP), they can be left with
@ -651,7 +692,7 @@ func (sa *SockaddrVM) sockaddr() (unsafe.Pointer, _Socklen, error) {
return unsafe.Pointer(&sa.raw), SizeofSockaddrVM, nil
}
func anyToSockaddr(rsa *RawSockaddrAny) (Sockaddr, error) {
func anyToSockaddr(fd int, rsa *RawSockaddrAny) (Sockaddr, error) {
switch rsa.Addr.Family {
case AF_NETLINK:
pp := (*RawSockaddrNetlink)(unsafe.Pointer(rsa))
@ -728,6 +769,30 @@ func anyToSockaddr(rsa *RawSockaddrAny) (Sockaddr, error) {
Port: pp.Port,
}
return sa, nil
case AF_BLUETOOTH:
proto, err := GetsockoptInt(fd, SOL_SOCKET, SO_PROTOCOL)
if err != nil {
return nil, err
}
// only BTPROTO_L2CAP and BTPROTO_RFCOMM can accept connections
switch proto {
case BTPROTO_L2CAP:
pp := (*RawSockaddrL2)(unsafe.Pointer(rsa))
sa := &SockaddrL2{
PSM: pp.Psm,
CID: pp.Cid,
Addr: pp.Bdaddr,
AddrType: pp.Bdaddr_type,
}
return sa, nil
case BTPROTO_RFCOMM:
pp := (*RawSockaddrRFCOMM)(unsafe.Pointer(rsa))
sa := &SockaddrRFCOMM{
Channel: pp.Channel,
Addr: pp.Bdaddr,
}
return sa, nil
}
}
return nil, EAFNOSUPPORT
}
@ -739,7 +804,7 @@ func Accept(fd int) (nfd int, sa Sockaddr, err error) {
if err != nil {
return
}
sa, err = anyToSockaddr(&rsa)
sa, err = anyToSockaddr(fd, &rsa)
if err != nil {
Close(nfd)
nfd = 0
@ -757,7 +822,7 @@ func Accept4(fd int, flags int) (nfd int, sa Sockaddr, err error) {
if len > SizeofSockaddrAny {
panic("RawSockaddrAny too small")
}
sa, err = anyToSockaddr(&rsa)
sa, err = anyToSockaddr(fd, &rsa)
if err != nil {
Close(nfd)
nfd = 0
@ -771,7 +836,7 @@ func Getsockname(fd int) (sa Sockaddr, err error) {
if err = getsockname(fd, &rsa, &len); err != nil {
return
}
return anyToSockaddr(&rsa)
return anyToSockaddr(fd, &rsa)
}
func GetsockoptIPMreqn(fd, level, opt int) (*IPMreqn, error) {
@ -960,7 +1025,7 @@ func Recvmsg(fd int, p, oob []byte, flags int) (n, oobn int, recvflags int, from
recvflags = int(msg.Flags)
// source address is only specified if the socket is unconnected
if rsa.Addr.Family != AF_UNSPEC {
from, err = anyToSockaddr(&rsa)
from, err = anyToSockaddr(fd, &rsa)
}
return
}

View File

@ -112,7 +112,7 @@ func Getsockname(fd int) (sa Sockaddr, err error) {
if err = getsockname(fd, &rsa, &len); err != nil {
return
}
return anyToSockaddr(&rsa)
return anyToSockaddr(fd, &rsa)
}
// GetsockoptString returns the string value of the socket option opt for the
@ -314,7 +314,11 @@ func UtimesNanoAt(dirfd int, path string, ts []Timespec, flags int) error {
// FcntlInt performs a fcntl syscall on fd with the provided command and argument.
func FcntlInt(fd uintptr, cmd, arg int) (int, error) {
valptr, _, err := sysvicall6(uintptr(unsafe.Pointer(&procfcntl)), 3, uintptr(fd), uintptr(cmd), uintptr(arg), 0, 0, 0)
valptr, _, errno := sysvicall6(uintptr(unsafe.Pointer(&procfcntl)), 3, uintptr(fd), uintptr(cmd), uintptr(arg), 0, 0, 0)
var err error
if errno != 0 {
err = errno
}
return int(valptr), err
}
@ -356,7 +360,7 @@ func Futimes(fd int, tv []Timeval) error {
return futimesat(fd, nil, (*[2]Timeval)(unsafe.Pointer(&tv[0])))
}
func anyToSockaddr(rsa *RawSockaddrAny) (Sockaddr, error) {
func anyToSockaddr(fd int, rsa *RawSockaddrAny) (Sockaddr, error) {
switch rsa.Addr.Family {
case AF_UNIX:
pp := (*RawSockaddrUnix)(unsafe.Pointer(rsa))
@ -407,7 +411,7 @@ func Accept(fd int) (nfd int, sa Sockaddr, err error) {
if nfd == -1 {
return
}
sa, err = anyToSockaddr(&rsa)
sa, err = anyToSockaddr(fd, &rsa)
if err != nil {
Close(nfd)
nfd = 0
@ -444,7 +448,7 @@ func Recvmsg(fd int, p, oob []byte, flags int) (n, oobn int, recvflags int, from
oobn = int(msg.Accrightslen)
// source address is only specified if the socket is unconnected
if rsa.Addr.Family != AF_UNSPEC {
from, err = anyToSockaddr(&rsa)
from, err = anyToSockaddr(fd, &rsa)
}
return
}

View File

@ -219,7 +219,7 @@ func Getpeername(fd int) (sa Sockaddr, err error) {
if err = getpeername(fd, &rsa, &len); err != nil {
return
}
return anyToSockaddr(&rsa)
return anyToSockaddr(fd, &rsa)
}
func GetsockoptByte(fd, level, opt int) (value byte, err error) {
@ -291,7 +291,7 @@ func Recvfrom(fd int, p []byte, flags int) (n int, from Sockaddr, err error) {
return
}
if rsa.Addr.Family != AF_UNSPEC {
from, err = anyToSockaddr(&rsa)
from, err = anyToSockaddr(fd, &rsa)
}
return
}

View File

@ -1474,6 +1474,21 @@ func Munlockall() (err error) {
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func faccessat(dirfd int, path string, mode uint32) (err error) {
var _p0 *byte
_p0, err = BytePtrFromString(path)
if err != nil {
return
}
_, _, e1 := Syscall(SYS_FACCESSAT, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(mode))
if e1 != 0 {
err = errnoErr(e1)
}
return
}
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func Dup2(oldfd int, newfd int) (err error) {
_, _, e1 := Syscall(SYS_DUP2, uintptr(oldfd), uintptr(newfd), 0)
if e1 != 0 {
@ -1495,21 +1510,6 @@ func EpollCreate(size int) (fd int, err error) {
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func faccessat(dirfd int, path string, mode uint32) (err error) {
var _p0 *byte
_p0, err = BytePtrFromString(path)
if err != nil {
return
}
_, _, e1 := Syscall(SYS_FACCESSAT, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(mode))
if e1 != 0 {
err = errnoErr(e1)
}
return
}
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) {
var _p0 unsafe.Pointer
if len(events) > 0 {

View File

@ -1474,6 +1474,21 @@ func Munlockall() (err error) {
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func faccessat(dirfd int, path string, mode uint32) (err error) {
var _p0 *byte
_p0, err = BytePtrFromString(path)
if err != nil {
return
}
_, _, e1 := Syscall(SYS_FACCESSAT, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(mode))
if e1 != 0 {
err = errnoErr(e1)
}
return
}
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func Dup2(oldfd int, newfd int) (err error) {
_, _, e1 := Syscall(SYS_DUP2, uintptr(oldfd), uintptr(newfd), 0)
if e1 != 0 {
@ -1495,21 +1510,6 @@ func EpollCreate(size int) (fd int, err error) {
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func faccessat(dirfd int, path string, mode uint32) (err error) {
var _p0 *byte
_p0, err = BytePtrFromString(path)
if err != nil {
return
}
_, _, e1 := Syscall(SYS_FACCESSAT, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(mode))
if e1 != 0 {
err = errnoErr(e1)
}
return
}
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) {
var _p0 unsafe.Pointer
if len(events) > 0 {

View File

@ -248,6 +248,13 @@ type RawSockaddrL2 struct {
_ [1]byte
}
type RawSockaddrRFCOMM struct {
Family uint16
Bdaddr [6]uint8
Channel uint8
_ [1]byte
}
type RawSockaddrCAN struct {
Family uint16
_ [2]byte
@ -401,6 +408,7 @@ const (
SizeofSockaddrNetlink = 0xc
SizeofSockaddrHCI = 0x6
SizeofSockaddrL2 = 0xe
SizeofSockaddrRFCOMM = 0xa
SizeofSockaddrCAN = 0x10
SizeofSockaddrALG = 0x58
SizeofSockaddrVM = 0x10

View File

@ -250,6 +250,13 @@ type RawSockaddrL2 struct {
_ [1]byte
}
type RawSockaddrRFCOMM struct {
Family uint16
Bdaddr [6]uint8
Channel uint8
_ [1]byte
}
type RawSockaddrCAN struct {
Family uint16
_ [2]byte
@ -405,6 +412,7 @@ const (
SizeofSockaddrNetlink = 0xc
SizeofSockaddrHCI = 0x6
SizeofSockaddrL2 = 0xe
SizeofSockaddrRFCOMM = 0xa
SizeofSockaddrCAN = 0x10
SizeofSockaddrALG = 0x58
SizeofSockaddrVM = 0x10

View File

@ -251,6 +251,13 @@ type RawSockaddrL2 struct {
_ [1]byte
}
type RawSockaddrRFCOMM struct {
Family uint16
Bdaddr [6]uint8
Channel uint8
_ [1]byte
}
type RawSockaddrCAN struct {
Family uint16
_ [2]byte
@ -404,6 +411,7 @@ const (
SizeofSockaddrNetlink = 0xc
SizeofSockaddrHCI = 0x6
SizeofSockaddrL2 = 0xe
SizeofSockaddrRFCOMM = 0xa
SizeofSockaddrCAN = 0x10
SizeofSockaddrALG = 0x58
SizeofSockaddrVM = 0x10

View File

@ -251,6 +251,13 @@ type RawSockaddrL2 struct {
_ [1]byte
}
type RawSockaddrRFCOMM struct {
Family uint16
Bdaddr [6]uint8
Channel uint8
_ [1]byte
}
type RawSockaddrCAN struct {
Family uint16
_ [2]byte
@ -406,6 +413,7 @@ const (
SizeofSockaddrNetlink = 0xc
SizeofSockaddrHCI = 0x6
SizeofSockaddrL2 = 0xe
SizeofSockaddrRFCOMM = 0xa
SizeofSockaddrCAN = 0x10
SizeofSockaddrALG = 0x58
SizeofSockaddrVM = 0x10

View File

@ -249,6 +249,13 @@ type RawSockaddrL2 struct {
_ [1]byte
}
type RawSockaddrRFCOMM struct {
Family uint16
Bdaddr [6]uint8
Channel uint8
_ [1]byte
}
type RawSockaddrCAN struct {
Family uint16
_ [2]byte
@ -402,6 +409,7 @@ const (
SizeofSockaddrNetlink = 0xc
SizeofSockaddrHCI = 0x6
SizeofSockaddrL2 = 0xe
SizeofSockaddrRFCOMM = 0xa
SizeofSockaddrCAN = 0x10
SizeofSockaddrALG = 0x58
SizeofSockaddrVM = 0x10

View File

@ -251,6 +251,13 @@ type RawSockaddrL2 struct {
_ [1]byte
}
type RawSockaddrRFCOMM struct {
Family uint16
Bdaddr [6]uint8
Channel uint8
_ [1]byte
}
type RawSockaddrCAN struct {
Family uint16
_ [2]byte
@ -406,6 +413,7 @@ const (
SizeofSockaddrNetlink = 0xc
SizeofSockaddrHCI = 0x6
SizeofSockaddrL2 = 0xe
SizeofSockaddrRFCOMM = 0xa
SizeofSockaddrCAN = 0x10
SizeofSockaddrALG = 0x58
SizeofSockaddrVM = 0x10

View File

@ -251,6 +251,13 @@ type RawSockaddrL2 struct {
_ [1]byte
}
type RawSockaddrRFCOMM struct {
Family uint16
Bdaddr [6]uint8
Channel uint8
_ [1]byte
}
type RawSockaddrCAN struct {
Family uint16
_ [2]byte
@ -406,6 +413,7 @@ const (
SizeofSockaddrNetlink = 0xc
SizeofSockaddrHCI = 0x6
SizeofSockaddrL2 = 0xe
SizeofSockaddrRFCOMM = 0xa
SizeofSockaddrCAN = 0x10
SizeofSockaddrALG = 0x58
SizeofSockaddrVM = 0x10

View File

@ -249,6 +249,13 @@ type RawSockaddrL2 struct {
_ [1]byte
}
type RawSockaddrRFCOMM struct {
Family uint16
Bdaddr [6]uint8
Channel uint8
_ [1]byte
}
type RawSockaddrCAN struct {
Family uint16
_ [2]byte
@ -402,6 +409,7 @@ const (
SizeofSockaddrNetlink = 0xc
SizeofSockaddrHCI = 0x6
SizeofSockaddrL2 = 0xe
SizeofSockaddrRFCOMM = 0xa
SizeofSockaddrCAN = 0x10
SizeofSockaddrALG = 0x58
SizeofSockaddrVM = 0x10

View File

@ -252,6 +252,13 @@ type RawSockaddrL2 struct {
_ [1]byte
}
type RawSockaddrRFCOMM struct {
Family uint16
Bdaddr [6]uint8
Channel uint8
_ [1]byte
}
type RawSockaddrCAN struct {
Family uint16
_ [2]byte
@ -407,6 +414,7 @@ const (
SizeofSockaddrNetlink = 0xc
SizeofSockaddrHCI = 0x6
SizeofSockaddrL2 = 0xe
SizeofSockaddrRFCOMM = 0xa
SizeofSockaddrCAN = 0x10
SizeofSockaddrALG = 0x58
SizeofSockaddrVM = 0x10

View File

@ -252,6 +252,13 @@ type RawSockaddrL2 struct {
_ [1]byte
}
type RawSockaddrRFCOMM struct {
Family uint16
Bdaddr [6]uint8
Channel uint8
_ [1]byte
}
type RawSockaddrCAN struct {
Family uint16
_ [2]byte
@ -407,6 +414,7 @@ const (
SizeofSockaddrNetlink = 0xc
SizeofSockaddrHCI = 0x6
SizeofSockaddrL2 = 0xe
SizeofSockaddrRFCOMM = 0xa
SizeofSockaddrCAN = 0x10
SizeofSockaddrALG = 0x58
SizeofSockaddrVM = 0x10

View File

@ -250,6 +250,13 @@ type RawSockaddrL2 struct {
_ [1]byte
}
type RawSockaddrRFCOMM struct {
Family uint16
Bdaddr [6]uint8
Channel uint8
_ [1]byte
}
type RawSockaddrCAN struct {
Family uint16
_ [2]byte
@ -405,6 +412,7 @@ const (
SizeofSockaddrNetlink = 0xc
SizeofSockaddrHCI = 0x6
SizeofSockaddrL2 = 0xe
SizeofSockaddrRFCOMM = 0xa
SizeofSockaddrCAN = 0x10
SizeofSockaddrALG = 0x58
SizeofSockaddrVM = 0x10

View File

@ -43,6 +43,11 @@ const (
SC_STATUS_PROCESS_INFO = 0
SC_ACTION_NONE = 0
SC_ACTION_RESTART = 1
SC_ACTION_REBOOT = 2
SC_ACTION_RUN_COMMAND = 3
SERVICE_STOPPED = 1
SERVICE_START_PENDING = 2
SERVICE_STOP_PENDING = 3
@ -148,6 +153,19 @@ type ENUM_SERVICE_STATUS_PROCESS struct {
ServiceStatusProcess SERVICE_STATUS_PROCESS
}
type SERVICE_FAILURE_ACTIONS struct {
ResetPeriod uint32
RebootMsg *uint16
Command *uint16
ActionsCount uint32
Actions *SC_ACTION
}
type SC_ACTION struct {
Type uint32
Delay uint32
}
//sys CloseServiceHandle(handle Handle) (err error) = advapi32.CloseServiceHandle
//sys CreateService(mgr Handle, serviceName *uint16, displayName *uint16, access uint32, srvType uint32, startType uint32, errCtl uint32, pathName *uint16, loadOrderGroup *uint16, tagId *uint32, dependencies *uint16, serviceStartName *uint16, password *uint16) (handle Handle, err error) [failretval==0] = advapi32.CreateServiceW
//sys OpenService(mgr Handle, serviceName *uint16, access uint32) (handle Handle, err error) [failretval==0] = advapi32.OpenServiceW

View File

@ -94,16 +94,29 @@ const (
FILE_APPEND_DATA = 0x00000004
FILE_WRITE_ATTRIBUTES = 0x00000100
FILE_SHARE_READ = 0x00000001
FILE_SHARE_WRITE = 0x00000002
FILE_SHARE_DELETE = 0x00000004
FILE_ATTRIBUTE_READONLY = 0x00000001
FILE_ATTRIBUTE_HIDDEN = 0x00000002
FILE_ATTRIBUTE_SYSTEM = 0x00000004
FILE_ATTRIBUTE_DIRECTORY = 0x00000010
FILE_ATTRIBUTE_ARCHIVE = 0x00000020
FILE_ATTRIBUTE_NORMAL = 0x00000080
FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400
FILE_SHARE_READ = 0x00000001
FILE_SHARE_WRITE = 0x00000002
FILE_SHARE_DELETE = 0x00000004
FILE_ATTRIBUTE_READONLY = 0x00000001
FILE_ATTRIBUTE_HIDDEN = 0x00000002
FILE_ATTRIBUTE_SYSTEM = 0x00000004
FILE_ATTRIBUTE_DIRECTORY = 0x00000010
FILE_ATTRIBUTE_ARCHIVE = 0x00000020
FILE_ATTRIBUTE_DEVICE = 0x00000040
FILE_ATTRIBUTE_NORMAL = 0x00000080
FILE_ATTRIBUTE_TEMPORARY = 0x00000100
FILE_ATTRIBUTE_SPARSE_FILE = 0x00000200
FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400
FILE_ATTRIBUTE_COMPRESSED = 0x00000800
FILE_ATTRIBUTE_OFFLINE = 0x00001000
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x00002000
FILE_ATTRIBUTE_ENCRYPTED = 0x00004000
FILE_ATTRIBUTE_INTEGRITY_STREAM = 0x00008000
FILE_ATTRIBUTE_VIRTUAL = 0x00010000
FILE_ATTRIBUTE_NO_SCRUB_DATA = 0x00020000
FILE_ATTRIBUTE_RECALL_ON_OPEN = 0x00040000
FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS = 0x00400000
INVALID_FILE_ATTRIBUTES = 0xffffffff

View File

@ -290,7 +290,7 @@ func (r *Runner) builtinCode(pos syntax.Pos, name string, args []string) int {
if parseErr {
return 2
}
return oneIf(r.bashTest(expr) == "")
return oneIf(r.bashTest(expr, true) == "")
case "exec":
// TODO: Consider syscall.Exec, i.e. actually replacing
// the process. It's in theory what a shell should do,

View File

@ -7,4 +7,4 @@
//
// This package is a work in progress and EXPERIMENTAL; its API is not
// subject to the 1.x backwards compatibility guarantee.
package interp // import "mvdan.cc/sh/interp"
package interp

View File

@ -247,7 +247,7 @@ func (r *Runner) wordField(wps []syntax.WordPart, ql quoteLevel) []fieldPart {
case '\n': // remove \\\n
i++
continue
case '\\', '$', '`': // special chars
case '"', '\\', '$', '`': // special chars
continue
}
}

View File

@ -602,7 +602,7 @@ func (r *Runner) cmd(cm syntax.Command) {
}
case *syntax.TestClause:
r.exit = 0
if r.bashTest(x.X) == "" && r.exit == 0 {
if r.bashTest(x.X, false) == "" && r.exit == 0 {
// to preserve exit code 2 for regex
// errors, etc
r.exit = 1

View File

@ -15,29 +15,36 @@ import (
)
// non-empty string is true, empty string is false
func (r *Runner) bashTest(expr syntax.TestExpr) string {
func (r *Runner) bashTest(expr syntax.TestExpr, classic bool) string {
switch x := expr.(type) {
case *syntax.Word:
return r.loneWord(x)
case *syntax.ParenTest:
return r.bashTest(x.X)
return r.bashTest(x.X, classic)
case *syntax.BinaryTest:
switch x.Op {
case syntax.TsMatch, syntax.TsNoMatch:
str := r.loneWord(x.X.(*syntax.Word))
yw := x.Y.(*syntax.Word)
pat := r.lonePattern(yw)
if match(pat, str) == (x.Op == syntax.TsMatch) {
return "1"
if classic { // test, [
lit := r.loneWord(yw)
if (str == lit) == (x.Op == syntax.TsMatch) {
return "1"
}
} else { // [[
pat := r.lonePattern(yw)
if match(pat, str) == (x.Op == syntax.TsMatch) {
return "1"
}
}
return ""
}
if r.binTest(x.Op, r.bashTest(x.X), r.bashTest(x.Y)) {
if r.binTest(x.Op, r.bashTest(x.X, classic), r.bashTest(x.Y, classic)) {
return "1"
}
return ""
case *syntax.UnaryTest:
if r.unTest(x.Op, r.bashTest(x.X)) {
if r.unTest(x.Op, r.bashTest(x.X, classic)) {
return "1"
}
return ""

View File

@ -6,4 +6,4 @@
//
// This package is a work in progress and EXPERIMENTAL; its API is not
// subject to the 1.x backwards compatibility guarantee.
package shell // import "mvdan.cc/sh/shell"
package shell

View File

@ -4,6 +4,7 @@
package shell
import (
"context"
"strings"
"mvdan.cc/sh/interp"
@ -19,23 +20,28 @@ import (
//
// Any side effects or modifications to the system are forbidden when
// interpreting the program. This is enforced via whitelists when
// executing programs and opening paths.
// executing programs and opening paths. The interpreter also has a timeout of
// two seconds.
func Expand(s string, env func(string) string) (string, error) {
p := syntax.NewParser()
src := "<<EOF\n" + s + "\nEOF"
src := "<<EXPAND_EOF\n" + s + "\nEXPAND_EOF"
f, err := p.Parse(strings.NewReader(src), "")
if err != nil {
return "", err
}
word := f.Stmts[0].Redirs[0].Hdoc
last := word.Parts[len(word.Parts)-1].(*syntax.Lit)
// since the heredoc implies a trailing newline
last.Value = strings.TrimSuffix(last.Value, "\n")
r := pureRunner()
if env != nil {
r.Env = interp.FuncEnviron(env)
}
r.Reset()
ctx, cancel := context.WithTimeout(context.Background(), pureRunnerTimeout)
defer cancel()
r.Context = ctx
fields := r.Fields(word)
// TODO: runner error
join := strings.Join(fields, "")
// since the heredoc implies a trailing newline
return strings.TrimSuffix(join, "\n"), nil
return strings.Join(fields, ""), nil
}

View File

@ -4,9 +4,11 @@
package shell
import (
"context"
"fmt"
"io"
"os"
"time"
"mvdan.cc/sh/interp"
"mvdan.cc/sh/syntax"
@ -44,6 +46,8 @@ var purePrograms = []string{
"env", "sleep", "uniq", "sort",
}
var pureRunnerTimeout = 2 * time.Second
func pureRunner() *interp.Runner {
r := &interp.Runner{}
// forbid executing programs that might cause trouble
@ -67,10 +71,14 @@ func pureRunner() *interp.Runner {
//
// Any side effects or modifications to the system are forbidden when
// interpreting the program. This is enforced via whitelists when
// executing programs and opening paths.
// executing programs and opening paths. The interpreter also has a timeout of
// two seconds.
func SourceNode(node syntax.Node) (map[string]interp.Variable, error) {
r := pureRunner()
r.Reset()
ctx, cancel := context.WithTimeout(context.Background(), pureRunnerTimeout)
defer cancel()
r.Context = ctx
if err := r.Run(node); err != nil {
return nil, fmt.Errorf("could not run: %v", err)
}

View File

@ -3,4 +3,4 @@
// Package syntax implements parsing and formatting of shell programs.
// It supports both POSIX Shell and Bash.
package syntax // import "mvdan.cc/sh/syntax"
package syntax

View File

@ -107,6 +107,7 @@ retry:
} else if p.fill(); p.bs == nil {
p.bsp++
p.r = utf8.RuneSelf
p.w = 1
} else {
goto retry
}
@ -232,6 +233,7 @@ skipSpace:
w := utf8.RuneLen(r)
if bytes.HasPrefix(p.bs[p.bsp-w:], p.stopAt) {
p.r = utf8.RuneSelf
p.w = 1
p.tok = _EOF
return
}
@ -748,10 +750,13 @@ func (p *Parser) endLit() (s string) {
return
}
func (p *Parser) numLit() bool {
for _, b := range p.litBs {
func (p *Parser) isLitRedir() bool {
lit := p.litBs[:len(p.litBs)-1]
if lit[0] == '{' && lit[len(lit)-1] == '}' {
return ValidName(string(lit[1 : len(lit)-1]))
}
for _, b := range lit {
switch b {
case '>', '<':
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
default:
return false
@ -800,7 +805,7 @@ loop:
break loop
}
case '/':
if p.quote&allParamExp != 0 && p.quote != paramExpExp {
if p.quote != paramExpExp {
break loop
}
case ':', '=', '%', '^', ',', '?', '!', '*':
@ -838,7 +843,7 @@ loop:
p.discardLit(2)
}
case '>', '<':
if p.peekByte('(') || !p.numLit() {
if p.peekByte('(') || !p.isLitRedir() {
tok = _Lit
} else {
tok = _LitRedir

View File

@ -232,7 +232,7 @@ func (a *Assign) End() Pos {
type Redirect struct {
OpPos Pos
Op RedirOperator
N *Lit // N>, must be a number
N *Lit // fd>, or {varname}> in Bash
Word *Word // >word
Hdoc *Word // here-document body
}

View File

@ -30,6 +30,18 @@ func Variant(l LangVariant) func(*Parser) {
return func(p *Parser) { p.lang = l }
}
func (l LangVariant) String() string {
switch l {
case LangBash:
return "bash"
case LangPOSIX:
return "posix"
case LangMirBSDKorn:
return "mksh"
}
return "unknown shell language variant"
}
// StopAt configures the lexer to stop at an arbitrary word, treating it
// as if it were the end of the input. It can contain any characters
// except whitespace, and cannot be over four bytes in size.
@ -480,26 +492,60 @@ func (p *Parser) errPass(err error) {
p.err = err
p.bsp = len(p.bs) + 1
p.r = utf8.RuneSelf
p.w = 1
p.tok = _EOF
}
}
// ParseError represents an error found when parsing a source file.
// ParseError represents an error found when parsing a source file, from which
// the parser cannot recover.
type ParseError struct {
Filename string
Pos
Text string
}
func (e *ParseError) Error() string {
func (e ParseError) Error() string {
if e.Filename == "" {
return fmt.Sprintf("%s: %s", e.Pos.String(), e.Text)
}
return fmt.Sprintf("%s:%s: %s", e.Filename, e.Pos.String(), e.Text)
}
// LangError is returned when the parser encounters code that is only valid in
// other shell language variants. The error includes what feature is not present
// in the current language variant, and what languages support it.
type LangError struct {
Filename string
Pos
Feature string
Langs []LangVariant
}
func (e LangError) Error() string {
var buf bytes.Buffer
if e.Filename != "" {
buf.WriteString(e.Filename + ":")
}
buf.WriteString(e.Pos.String() + ": ")
buf.WriteString(e.Feature)
if strings.HasSuffix(e.Feature, "s") {
buf.WriteString(" are a ")
} else {
buf.WriteString(" is a ")
}
for i, lang := range e.Langs {
if i > 0 {
buf.WriteString("/")
}
buf.WriteString(lang.String())
}
buf.WriteString(" feature")
return buf.String()
}
func (p *Parser) posErr(pos Pos, format string, a ...interface{}) {
p.errPass(&ParseError{
p.errPass(ParseError{
Filename: p.f.Name,
Pos: pos,
Text: fmt.Sprintf(format, a...),
@ -510,6 +556,15 @@ func (p *Parser) curErr(format string, a ...interface{}) {
p.posErr(p.pos, format, a...)
}
func (p *Parser) langErr(pos Pos, feature string, langs ...LangVariant) {
p.errPass(LangError{
Filename: p.f.Name,
Pos: pos,
Feature: feature,
Langs: langs,
})
}
func (p *Parser) stmts(fn func(*Stmt) bool, stops ...string) {
gotEnd := true
loop:
@ -668,7 +723,7 @@ func (p *Parser) wordPart() WordPart {
p.next()
if p.got(hash) {
if p.lang != LangMirBSDKorn {
p.posErr(ar.Pos(), "unsigned expressions are a mksh feature")
p.langErr(ar.Pos(), "unsigned expressions", LangMirBSDKorn)
}
ar.Unsigned = true
}
@ -712,9 +767,16 @@ func (p *Parser) wordPart() WordPart {
p.pos = posAddCol(p.pos, 1)
pe.Param = p.getLit()
if pe.Param != nil && pe.Param.Value == "" {
// e.g. "$\\\n", which we can't detect above
l := p.lit(pe.Dollar, "$")
p.next()
if p.val == "" {
// e.g. "$\\\n" followed by a closing double
// quote, so we need the next token.
p.next()
} else {
// e.g. "$\\\"" within double quotes, so we must
// keep the rest of the literal characters.
l.ValueEnd = posAddCol(l.ValuePos, 1)
}
return l
}
return pe
@ -777,7 +839,7 @@ func (p *Parser) wordPart() WordPart {
return cs
case globQuest, globStar, globPlus, globAt, globExcl:
if p.lang == LangPOSIX {
p.curErr("extended globs are a bash feature")
p.langErr(p.pos, "extended globs", LangBash, LangMirBSDKorn)
}
eg := &ExtGlob{Op: GlobOperator(p.tok), OpPos: p.pos}
lparens := 1
@ -1058,7 +1120,7 @@ func (p *Parser) paramExp() *ParamExp {
case exclMark:
if paramNameOp(p.r) {
if p.lang == LangPOSIX {
p.curErr("${!foo} is a bash feature")
p.langErr(p.pos, "${!foo}", LangBash, LangMirBSDKorn)
}
pe.Excl = true
p.next()
@ -1067,6 +1129,9 @@ func (p *Parser) paramExp() *ParamExp {
op := p.tok
switch p.tok {
case _Lit, _LitWord:
if !numberLiteral(p.val) && !ValidName(p.val) {
p.curErr("invalid parameter name")
}
pe.Param = p.lit(p.pos, p.val)
p.next()
case quest, minus:
@ -1093,7 +1158,7 @@ func (p *Parser) paramExp() *ParamExp {
return pe
case leftBrack:
if p.lang == LangPOSIX {
p.curErr("arrays are a bash feature")
p.langErr(p.pos, "arrays", LangBash, LangMirBSDKorn)
}
if !ValidName(pe.Param.Value) {
p.curErr("cannot index a special parameter name")
@ -1113,7 +1178,7 @@ func (p *Parser) paramExp() *ParamExp {
case slash, dblSlash:
// pattern search and replace
if p.lang == LangPOSIX {
p.curErr("search and replace is a bash feature")
p.langErr(p.pos, "search and replace", LangBash, LangMirBSDKorn)
}
pe.Repl = &Replace{All: p.tok == dblSlash}
p.quote = paramExpRepl
@ -1126,7 +1191,7 @@ func (p *Parser) paramExp() *ParamExp {
case colon:
// slicing
if p.lang == LangPOSIX {
p.curErr("slicing is a bash feature")
p.langErr(p.pos, "slicing", LangBash, LangMirBSDKorn)
}
pe.Slice = &Slice{}
colonPos := p.pos
@ -1141,13 +1206,13 @@ func (p *Parser) paramExp() *ParamExp {
case caret, dblCaret, comma, dblComma:
// upper/lower case
if p.lang != LangBash {
p.curErr("this expansion operator is a bash feature")
p.langErr(p.pos, "this expansion operator", LangBash)
}
pe.Exp = p.paramExpExp()
case at, star:
switch {
case p.tok == at && p.lang == LangPOSIX:
p.curErr("this expansion operator is a bash feature")
p.langErr(p.pos, "this expansion operator", LangBash, LangMirBSDKorn)
case p.tok == star && !pe.Excl:
p.curErr("not a valid parameter expansion operator: %v", p.tok)
case pe.Excl:
@ -1252,6 +1317,15 @@ func ValidName(val string) bool {
return true
}
func numberLiteral(val string) bool {
for _, r := range val {
if '0' > r || r > '9' {
return false
}
}
return true
}
func (p *Parser) hasValidIdent() bool {
if p.tok != _Lit && p.tok != _LitWord {
return false
@ -1319,7 +1393,7 @@ func (p *Parser) getAssign(needEqual bool) *Assign {
}
if as.Value == nil && p.tok == leftParen {
if p.lang == LangPOSIX {
p.curErr("arrays are a bash feature")
p.langErr(p.pos, "arrays", LangBash, LangMirBSDKorn)
}
if as.Index != nil {
p.curErr("arrays cannot be nested")
@ -1394,6 +1468,9 @@ func (p *Parser) doRedirect(s *Stmt) {
s.Redirs = append(s.Redirs, r)
}
r.N = p.getLit()
if p.lang != LangBash && r.N != nil && r.N.Value[0] == '{' {
p.langErr(r.N.Pos(), "{varname} redirects", LangBash)
}
r.Op, r.OpPos = RedirOperator(p.tok), p.pos
p.next()
switch r.Op {
@ -1643,7 +1720,7 @@ func (p *Parser) arithmExpCmd(s *Stmt) {
p.next()
if p.got(hash) {
if p.lang != LangMirBSDKorn {
p.posErr(ar.Pos(), "unsigned expressions are a mksh feature")
p.langErr(ar.Pos(), "unsigned expressions", LangMirBSDKorn)
}
ar.Unsigned = true
}
@ -1725,7 +1802,7 @@ func (p *Parser) loop(fpos Pos) Loop {
if p.lang != LangBash {
switch p.tok {
case leftParen, dblLeftParen:
p.curErr("c-style fors are a bash feature")
p.langErr(p.pos, "c-style fors", LangBash)
}
}
if p.tok == dblLeftParen {
@ -1909,7 +1986,7 @@ func (p *Parser) testExpr(ftok token, fpos Pos, pastAndOr bool) TestExpr {
}
case TsReMatch:
if p.lang != LangBash {
p.curErr("regex tests are a bash feature")
p.langErr(p.pos, "regex tests", LangBash)
}
oldReOpenParens := p.reOpenParens
old := p.preNested(testRegexp)

View File

@ -68,17 +68,12 @@ func NewPrinter(options ...func(*Printer)) *Printer {
func (p *Printer) Print(w io.Writer, node Node) error {
p.reset()
p.bufWriter.Reset(w)
p.line = node.Pos().Line()
switch x := node.(type) {
case *File:
p.stmts(x.StmtList)
p.stmtList(x.StmtList)
p.newline(x.End())
case *Stmt:
sl := StmtList{Stmts: []*Stmt{x}}
// StmtList.pos is better than Stmt.Pos, since it also takes
// comments into account.
p.line = sl.pos().Line()
p.stmts(sl)
p.stmtList(StmtList{Stmts: []*Stmt{x}})
case *Word:
p.word(x)
case Command:
@ -152,6 +147,8 @@ type Printer struct {
// comment in the same line, breaking programs.
pendingComments []Comment
// firstLine means we are still writing the first line
firstLine bool
// line is the current line number
line uint
@ -180,7 +177,11 @@ func (p *Printer) reset() {
p.wantSpace, p.wantNewline = false, false
p.commentPadding = 0
p.pendingComments = p.pendingComments[:0]
// minification uses its own newline logic
p.firstLine = !p.minify
p.line = 0
p.lastLevel, p.level = 0, 0
p.levelIncs = p.levelIncs[:0]
p.nestedBinary = false
@ -354,7 +355,8 @@ func (p *Printer) flushHeredocs() {
}
func (p *Printer) newlines(pos Pos) {
if !p.wantNewline && p.line == 0 && len(p.pendingComments) == 0 {
if p.firstLine && len(p.pendingComments) == 0 {
p.firstLine = false
return // no empty lines at the top
}
if !p.wantNewline && pos.Line() <= p.line {
@ -379,11 +381,11 @@ func (p *Printer) rightParen(pos Pos) {
p.wantSpace = true
}
func (p *Printer) semiRsrv(s string, pos Pos, fallback bool) {
func (p *Printer) semiRsrv(s string, pos Pos) {
if p.wantNewline || pos.Line() > p.line {
p.newlines(pos)
} else {
if fallback && !p.wroteSemi {
if !p.wroteSemi {
p.WriteByte(';')
}
if !p.minify {
@ -403,6 +405,9 @@ func (p *Printer) comment(c Comment) {
func (p *Printer) flushComments() {
for i, c := range p.pendingComments {
p.firstLine = false
// We can't call any of the newline methods, as they call this
// function and we'd recurse forever.
cline := c.Hash.Line()
switch {
case i > 0, cline > p.line && p.line > 0:
@ -430,6 +435,9 @@ func (p *Printer) flushComments() {
}
func (p *Printer) comments(cs []Comment) {
if p.minify {
return
}
p.pendingComments = append(p.pendingComments, cs...)
}
@ -465,12 +473,12 @@ func (p *Printer) wordPart(wp, next WordPart) {
p.wantSpace = true
p.nestedStmts(x.StmtList, x.Right)
p.wantSpace = false
p.semiRsrv("}", x.Right, true)
p.semiRsrv("}", x.Right)
case x.ReplyVar:
p.WriteString("${|")
p.nestedStmts(x.StmtList, x.Right)
p.wantSpace = false
p.semiRsrv("}", x.Right, true)
p.semiRsrv("}", x.Right)
default:
p.WriteString("$(")
p.wantSpace = len(x.Stmts) > 0 && startsWithLparen(x.Stmts[0])
@ -478,14 +486,19 @@ func (p *Printer) wordPart(wp, next WordPart) {
p.rightParen(x.Right)
}
case *ParamExp:
nextLit, ok := next.(*Lit)
litCont := ";"
if ok {
if nextLit, ok := next.(*Lit); ok {
litCont = nextLit.Value[:1]
}
if p.minify && !x.Excl && !x.Length && !x.Width &&
x.Index == nil && x.Slice == nil && x.Repl == nil &&
x.Exp == nil && !ValidName(x.Param.Value+litCont) {
name := x.Param.Value
switch {
case !p.minify:
case x.Excl, x.Length, x.Width:
case x.Index != nil, x.Slice != nil:
case x.Repl != nil, x.Exp != nil:
case len(name) > 1 && !ValidName(name): // ${10}
case ValidName(name + litCont): // ${var}cont
default:
x2 := *x
x2.Short = true
p.paramExp(&x2)
@ -510,7 +523,7 @@ func (p *Printer) wordPart(wp, next WordPart) {
}
p.WriteString(x.Op.String())
p.nestedStmts(x.StmtList, x.Rparen)
p.WriteByte(')')
p.rightParen(x.Rparen)
}
}
@ -775,6 +788,7 @@ func (p *Printer) elemJoin(elems []*ArrayElem, last []Comment) {
}
func (p *Printer) stmt(s *Stmt) {
p.wroteSemi = false
if s.Negated {
p.spacedString("!", s.Pos())
}
@ -787,8 +801,7 @@ func (p *Printer) stmt(s *Stmt) {
if r.OpPos.Line() > p.line {
p.bslashNewl()
}
if p.minify && r.N == nil {
} else if p.wantSpace {
if p.wantSpace {
p.spacePad(r.Pos())
}
if r.N != nil {
@ -805,7 +818,6 @@ func (p *Printer) stmt(s *Stmt) {
p.pendingHdocs = append(p.pendingHdocs, r)
}
}
p.wroteSemi = false
switch {
case s.Semicolon.IsValid() && s.Semicolon.Line() > p.line:
p.bslashNewl()
@ -839,8 +851,7 @@ func (p *Printer) command(cmd Command, redirs []*Redirect) (startRedirs int) {
if r.Pos().After(x.Args[1].Pos()) || r.Op == Hdoc || r.Op == DashHdoc {
break
}
if p.minify && r.N == nil {
} else if p.wantSpace {
if p.wantSpace {
p.spacePad(r.Pos())
}
if r.N != nil {
@ -860,7 +871,7 @@ func (p *Printer) command(cmd Command, redirs []*Redirect) (startRedirs int) {
p.WriteByte('{')
p.wantSpace = true
p.nestedStmts(x.StmtList, x.Rbrace)
p.semiRsrv("}", x.Rbrace, true)
p.semiRsrv("}", x.Rbrace)
case *IfClause:
p.ifClause(x, false)
case *Subshell:
@ -880,7 +891,7 @@ func (p *Printer) command(cmd Command, redirs []*Redirect) (startRedirs int) {
p.nestedStmts(x.Cond, Pos{})
p.semiOrNewl("do", x.DoPos)
p.nestedStmts(x.Do, x.DonePos)
p.semiRsrv("done", x.DonePos, true)
p.semiRsrv("done", x.DonePos)
case *ForClause:
if x.Select {
p.WriteString("select ")
@ -890,7 +901,7 @@ func (p *Printer) command(cmd Command, redirs []*Redirect) (startRedirs int) {
p.loop(x.Loop)
p.semiOrNewl("do", x.DoPos)
p.nestedStmts(x.Do, x.DonePos)
p.semiRsrv("done", x.DonePos, true)
p.semiRsrv("done", x.DonePos)
case *BinaryCmd:
p.stmt(x.X)
if p.minify || x.Y.Pos().Line() <= p.line {
@ -973,6 +984,8 @@ func (p *Printer) command(cmd Command, redirs []*Redirect) (startRedirs int) {
p.wantNewline = true
}
p.spacedToken(ci.Op.String(), ci.OpPos)
// avoid ; directly after tokens like ;;
p.wroteSemi = true
if inlineCom != nil {
p.comment(*inlineCom)
}
@ -984,7 +997,7 @@ func (p *Printer) command(cmd Command, redirs []*Redirect) (startRedirs int) {
p.flushComments()
p.decLevel()
}
p.semiRsrv("esac", x.Esac, len(x.Items) == 0)
p.semiRsrv("esac", x.Esac)
case *ArithmCmd:
p.WriteString("((")
if x.Unsigned {
@ -1037,17 +1050,17 @@ func (p *Printer) ifClause(ic *IfClause, elif bool) {
p.semiOrNewl("then", ic.ThenPos)
p.nestedStmts(ic.Then, ic.bodyEndPos())
if ic.FollowedByElif() {
p.semiRsrv("elif", ic.ElsePos, true)
p.semiRsrv("elif", ic.ElsePos)
p.ifClause(ic.Else.Stmts[0].Cmd.(*IfClause), true)
return
}
if !ic.Else.empty() {
p.semiRsrv("else", ic.ElsePos, true)
p.semiRsrv("else", ic.ElsePos)
p.nestedStmts(ic.Else, ic.FiPos)
} else if ic.ElsePos.IsValid() {
p.line = ic.ElsePos.Line()
}
p.semiRsrv("fi", ic.FiPos, true)
p.semiRsrv("fi", ic.FiPos)
}
func startsWithLparen(s *Stmt) bool {
@ -1069,7 +1082,7 @@ func (p *Printer) hasInline(s *Stmt) bool {
return false
}
func (p *Printer) stmts(sl StmtList) {
func (p *Printer) stmtList(sl StmtList) {
sep := p.wantNewline ||
(len(sl.Stmts) > 0 && sl.Stmts[0].Pos().Line() > p.line)
inlineIndent := 0
@ -1216,7 +1229,7 @@ func (p *Printer) nestedStmts(sl StmtList, closing Pos) {
// do foo; done
p.wantNewline = true
}
p.stmts(sl)
p.stmtList(sl)
if closing.IsValid() {
p.flushComments()
}