Merge component 'cli' from git@github.com:docker/cli master

This commit is contained in:
Andrew Hsu
2017-07-19 23:39:56 +00:00
32 changed files with 533 additions and 178 deletions

View File

@ -1,8 +1,8 @@
# Github code owners
# See https://github.com/blog/2392-introducing-code-owners
cli/compose/* @dnephin @vdemeester
contrib/completion/bash/* @albers
contrib/completion/zsh/* @sdurrheimer
docs/* @mstanleyjones @vdemeester @thaJeztah
scripts/* @dnephin
cli/compose/** @dnephin @vdemeester
contrib/completion/bash/** @albers
contrib/completion/zsh/** @sdurrheimer
docs/** @mstanleyjones @vdemeester @thaJeztah
scripts/** @dnephin

View File

@ -54,6 +54,11 @@ manpages:
yamldocs:
scripts/docs/generate-yaml.sh
## Shellcheck validation
.PHONY: shellcheck
shellcheck:
scripts/validate/shellcheck
cli/compose/schema/bindata.go: cli/compose/schema/data/*.json
go generate github.com/docker/cli/cli/compose/schema

View File

@ -4,7 +4,7 @@ jobs:
lint:
working_directory: /work
docker: [{image: 'docker:17.05-git'}]
docker: [{image: 'docker:17.06-git'}]
steps:
- checkout
- setup_remote_docker:
@ -22,7 +22,7 @@ jobs:
cross:
working_directory: /work
docker: [{image: 'docker:17.05-git'}]
docker: [{image: 'docker:17.06-git'}]
parallelism: 3
steps:
- checkout
@ -48,7 +48,7 @@ jobs:
test:
working_directory: /work
docker: [{image: 'docker:17.05-git'}]
docker: [{image: 'docker:17.06-git'}]
steps:
- checkout
- setup_remote_docker:
@ -75,9 +75,8 @@ jobs:
validate:
working_directory: /work
docker: [{image: 'docker:17.05-git'}]
docker: [{image: 'docker:17.06-git'}]
steps:
- run: apk add -U git openssh
- checkout
- setup_remote_docker:
reusable: true
@ -91,7 +90,20 @@ jobs:
docker build -f $dockerfile --tag cli-builder-with-git:$CIRCLE_BUILD_NUM .
docker run --rm cli-builder-with-git:$CIRCLE_BUILD_NUM \
make -B vendor compose-jsonschema manpages yamldocs
shellcheck:
working_directory: /work
docker: [{image: 'docker:17.06-git'}]
steps:
- checkout
- setup_remote_docker
- run:
name: "Run shellcheck"
command: |
dockerfile=dockerfiles/Dockerfile.shellcheck
echo "COPY . ." >> $dockerfile
docker build -f $dockerfile --tag cli-validator:$CIRCLE_BUILD_NUM .
docker run --rm cli-validator:$CIRCLE_BUILD_NUM \
make -B shellcheck
workflows:
version: 2
ci:
@ -100,3 +112,4 @@ workflows:
- cross
- test
- validate
- shellcheck

View File

@ -1,15 +1,27 @@
package node
import (
"sort"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/spf13/cobra"
"golang.org/x/net/context"
"vbom.ml/util/sortorder"
)
type byHostname []swarm.Node
func (n byHostname) Len() int { return len(n) }
func (n byHostname) Swap(i, j int) { n[i], n[j] = n[j], n[i] }
func (n byHostname) Less(i, j int) bool {
return sortorder.NaturalLess(n[i].Description.Hostname, n[j].Description.Hostname)
}
type listOptions struct {
quiet bool
format string
@ -68,5 +80,6 @@ func runList(dockerCli command.Cli, options listOptions) error {
Output: dockerCli.Out(),
Format: formatter.NewNodeFormat(format, options.quiet),
}
sort.Sort(byHostname(nodes))
return formatter.NodeWrite(nodesCtx, nodes, info)
}

View File

@ -9,6 +9,8 @@ import (
"github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/pkg/testutil"
"github.com/docker/docker/pkg/testutil/golden"
"github.com/pkg/errors"
// Import builders to get the builder function as package function
. "github.com/docker/cli/cli/internal/test/builders"
@ -156,3 +158,22 @@ func TestNodeListFormat(t *testing.T) {
assert.Contains(t, buf.String(), `nodeHostname1: Leader`)
assert.Contains(t, buf.String(), `nodeHostname2: Reachable`)
}
func TestNodeListOrder(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
nodeListFunc: func() ([]swarm.Node, error) {
return []swarm.Node{
*Node(Hostname("node-2-foo"), Manager(Leader())),
*Node(Hostname("node-10-foo"), Manager()),
*Node(Hostname("node-1-foo")),
}, nil
},
})
cmd := newListCommand(cli)
cmd.Flags().Set("format", "{{.Hostname}}: {{.ManagerStatus}}")
assert.NoError(t, cmd.Execute())
actual := cli.OutBuffer().String()
expected := golden.Get(t, []byte(actual), "node-list-sort.golden")
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected))
}

View File

@ -0,0 +1,3 @@
node-1-foo:
node-2-foo: Leader
node-10-foo: Reachable

View File

@ -1,30 +0,0 @@
package prune
import (
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/container"
"github.com/docker/cli/cli/command/image"
"github.com/docker/cli/cli/command/network"
"github.com/docker/cli/cli/command/volume"
"github.com/docker/cli/opts"
)
// RunContainerPrune executes a prune command for containers
func RunContainerPrune(dockerCli command.Cli, filter opts.FilterOpt) (uint64, string, error) {
return container.RunPrune(dockerCli, filter)
}
// RunVolumePrune executes a prune command for volumes
func RunVolumePrune(dockerCli command.Cli, filter opts.FilterOpt) (uint64, string, error) {
return volume.RunPrune(dockerCli, filter)
}
// RunImagePrune executes a prune command for images
func RunImagePrune(dockerCli command.Cli, all bool, filter opts.FilterOpt) (uint64, string, error) {
return image.RunPrune(dockerCli, all, filter)
}
// RunNetworkPrune executes a prune command for networks
func RunNetworkPrune(dockerCli command.Cli, filter opts.FilterOpt) (uint64, string, error) {
return network.RunPrune(dockerCli, filter)
}

View File

@ -12,6 +12,7 @@ import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
"golang.org/x/net/context"
"vbom.ml/util/sortorder"
)
type listOptions struct {
@ -60,7 +61,7 @@ type byName []*formatter.Stack
func (n byName) Len() int { return len(n) }
func (n byName) Swap(i, j int) { n[i], n[j] = n[j], n[i] }
func (n byName) Less(i, j int) bool { return n[i].Name < n[j].Name }
func (n byName) Less(i, j int) bool { return sortorder.NaturalLess(n[i].Name, n[j].Name) }
func getStacks(ctx context.Context, apiclient client.APIClient) ([]*formatter.Stack, error) {
services, err := apiclient.ServiceList(

View File

@ -98,10 +98,13 @@ func TestListWithoutFormat(t *testing.T) {
}
func TestListOrder(t *testing.T) {
buf := new(bytes.Buffer)
cmd := newListCommand(test.NewFakeCliWithOutput(&fakeClient{
serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
return []swarm.Service{
usecases := []struct {
golden string
swarmServices []swarm.Service
}{
{
golden: "stack-list-sort.golden",
swarmServices: []swarm.Service{
*Service(
ServiceLabels(map[string]string{
"com.docker.stack.namespace": "service-name-foo",
@ -112,11 +115,40 @@ func TestListOrder(t *testing.T) {
"com.docker.stack.namespace": "service-name-bar",
}),
),
}, nil
},
},
}, buf))
assert.NoError(t, cmd.Execute())
actual := buf.String()
expected := golden.Get(t, []byte(actual), "stack-list-sort.golden")
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected))
{
golden: "stack-list-sort-natural.golden",
swarmServices: []swarm.Service{
*Service(
ServiceLabels(map[string]string{
"com.docker.stack.namespace": "service-name-1-foo",
}),
),
*Service(
ServiceLabels(map[string]string{
"com.docker.stack.namespace": "service-name-10-foo",
}),
),
*Service(
ServiceLabels(map[string]string{
"com.docker.stack.namespace": "service-name-2-foo",
}),
),
},
},
}
for _, uc := range usecases {
buf := new(bytes.Buffer)
cmd := newListCommand(test.NewFakeCliWithOutput(&fakeClient{
serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
return uc.swarmServices, nil
},
}, buf))
assert.NoError(t, cmd.Execute())
actual := buf.String()
expected := golden.Get(t, []byte(actual), uc.golden)
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected))
}
}

View File

@ -0,0 +1,4 @@
NAME SERVICES
service-name-1-foo 1
service-name-2-foo 1
service-name-10-foo 1

View File

@ -0,0 +1,15 @@
package system
import (
"github.com/docker/docker/client"
)
type fakeClient struct {
client.Client
version string
}
func (cli *fakeClient) ClientVersion() string {
return cli.version
}

View File

@ -18,8 +18,8 @@ func NewSystemCommand(dockerCli *command.DockerCli) *cobra.Command {
cmd.AddCommand(
NewEventsCommand(dockerCli),
NewInfoCommand(dockerCli),
NewDiskUsageCommand(dockerCli),
NewPruneCommand(dockerCli),
newDiskUsageCommand(dockerCli),
newPruneCommand(dockerCli),
)
return cmd

View File

@ -15,8 +15,8 @@ type diskUsageOptions struct {
format string
}
// NewDiskUsageCommand creates a new cobra.Command for `docker df`
func NewDiskUsageCommand(dockerCli *command.DockerCli) *cobra.Command {
// newDiskUsageCommand creates a new cobra.Command for `docker df`
func newDiskUsageCommand(dockerCli *command.DockerCli) *cobra.Command {
var opts diskUsageOptions
cmd := &cobra.Command{

View File

@ -7,23 +7,28 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/prune"
"github.com/docker/cli/cli/command/container"
"github.com/docker/cli/cli/command/image"
"github.com/docker/cli/cli/command/network"
"github.com/docker/cli/cli/command/volume"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types/versions"
units "github.com/docker/go-units"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)
type pruneOptions struct {
force bool
all bool
pruneVolumes bool
filter opts.FilterOpt
force bool
all bool
pruneBuildCache bool
pruneVolumes bool
filter opts.FilterOpt
}
// NewPruneCommand creates a new cobra.Command for `docker prune`
func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
options := pruneOptions{filter: opts.NewFilterOpt()}
// newPruneCommand creates a new cobra.Command for `docker prune`
func newPruneCommand(dockerCli command.Cli) *cobra.Command {
options := pruneOptions{filter: opts.NewFilterOpt(), pruneBuildCache: true}
cmd := &cobra.Command{
Use: "prune [OPTIONS]",
@ -52,20 +57,38 @@ const confirmationTemplate = `WARNING! This will remove:
{{- end }}
Are you sure you want to continue?`
// runBuildCachePrune executes a prune command for build cache
func runBuildCachePrune(dockerCli command.Cli, _ opts.FilterOpt) (uint64, string, error) {
report, err := dockerCli.Client().BuildCachePrune(context.Background())
if err != nil {
return 0, "", err
}
return report.SpaceReclaimed, "", nil
}
func runPrune(dockerCli command.Cli, options pruneOptions) error {
if versions.LessThan(dockerCli.Client().ClientVersion(), "1.31") {
options.pruneBuildCache = false
}
if !options.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), confirmationMessage(options)) {
return nil
}
var spaceReclaimed uint64
imagePrune := func(dockerCli command.Cli, filter opts.FilterOpt) (uint64, string, error) {
return image.RunPrune(dockerCli, options.all, options.filter)
}
pruneFuncs := []func(dockerCli command.Cli, filter opts.FilterOpt) (uint64, string, error){
prune.RunContainerPrune,
prune.RunNetworkPrune,
container.RunPrune,
network.RunPrune,
}
if options.pruneVolumes {
pruneFuncs = append(pruneFuncs, prune.RunVolumePrune)
pruneFuncs = append(pruneFuncs, volume.RunPrune)
}
pruneFuncs = append(pruneFuncs, imagePrune)
if options.pruneBuildCache {
pruneFuncs = append(pruneFuncs, runBuildCachePrune)
}
var spaceReclaimed uint64
for _, pruneFn := range pruneFuncs {
spc, output, err := pruneFn(dockerCli, options.filter)
if err != nil {
@ -77,21 +100,6 @@ func runPrune(dockerCli command.Cli, options pruneOptions) error {
}
}
spc, output, err := prune.RunImagePrune(dockerCli, options.all, options.filter)
if err != nil {
return err
}
if spc > 0 {
spaceReclaimed += spc
fmt.Fprintln(dockerCli.Out(), output)
}
report, err := dockerCli.Client().BuildCachePrune(context.Background())
if err != nil {
return err
}
spaceReclaimed += report.SpaceReclaimed
fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed)))
return nil
@ -113,7 +121,9 @@ func confirmationMessage(options pruneOptions) string {
} else {
warnings = append(warnings, "all dangling images")
}
warnings = append(warnings, "all build cache")
if options.pruneBuildCache {
warnings = append(warnings, "all build cache")
}
var buffer bytes.Buffer
t.Execute(&buffer, &warnings)

View File

@ -0,0 +1,15 @@
package system
import (
"testing"
"github.com/docker/cli/cli/internal/test"
"github.com/stretchr/testify/assert"
)
func TestPrunePromptPre131(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{version: "1.30"})
cmd := newPruneCommand(cli)
assert.NoError(t, cmd.Execute())
assert.NotContains(t, cli.OutBuffer().String(), "all build cache")
}

View File

@ -1,4 +1,13 @@
#!/usr/bin/env bash
# shellcheck disable=SC2016,SC2119,SC2155
#
# Shellcheck ignore list:
# - SC2016: Expressions don't expand in single quotes, use double quotes for that.
# - SC2119: Use foo "$@" if function's $1 should mean script's $1.
# - SC2155: Declare and assign separately to avoid masking return values.
#
# You can find more details for each warning at the following page:
# https://github.com/koalaman/shellcheck/wiki/<SCXXXX>
#
# bash completion file for core docker commands
#
@ -101,6 +110,7 @@ __docker_complete_containers_all() {
__docker_complete_containers "$@" --all
}
# shellcheck disable=SC2120
__docker_complete_containers_removable() {
__docker_complete_containers "$@" --filter status=created --filter status=exited
}
@ -109,10 +119,12 @@ __docker_complete_containers_running() {
__docker_complete_containers "$@" --filter status=running
}
# shellcheck disable=SC2120
__docker_complete_containers_stopped() {
__docker_complete_containers "$@" --filter status=exited
}
# shellcheck disable=SC2120
__docker_complete_containers_unpauseable() {
__docker_complete_containers "$@" --filter status=paused
}
@ -213,6 +225,7 @@ __docker_complete_networks() {
COMPREPLY=( $(compgen -W "$(__docker_networks "$@")" -- "$current") )
}
# shellcheck disable=SC2128,SC2178
__docker_complete_containers_in_network() {
local containers=$(__docker_q network inspect -f '{{range $i, $c := .Containers}}{{$i}} {{$c.Name}} {{end}}' "$1")
COMPREPLY=( $(compgen -W "$containers" -- "$cur") )
@ -271,6 +284,7 @@ __docker_plugins_bundled() {
for del in "${remove[@]}" ; do
plugins=(${plugins[@]/$del/})
done
# shellcheck disable=SC2145
echo "${plugins[@]} ${add[@]}"
}
@ -414,7 +428,7 @@ __docker_nodes() {
esac
done
echo $(__docker_q node ls "$@" | tr -d '*' | awk "NR>1 {print $fields}") "${add[@]}"
echo "$(__docker_q node ls "$@" | tr -d '*' | awk "NR>1 {print $fields}")" "${add[@]}"
}
# __docker_complete_nodes applies completion of nodes based on the current
@ -469,6 +483,7 @@ __docker_tasks() {
}
# __docker_complete_services_and_tasks applies completion of services and task IDs.
# shellcheck disable=SC2120
__docker_complete_services_and_tasks() {
COMPREPLY=( $(compgen -W "$(__docker_services "$@") $(__docker_tasks)" -- "$cur") )
}
@ -509,7 +524,7 @@ __docker_pos_first_nonflag() {
local argument_flags=$1
local counter=$((${subcommand_pos:-${command_pos}} + 1))
while [ $counter -le $cword ]; do
while [ "$counter" -le "$cword" ]; do
if [ -n "$argument_flags" ] && eval "case '${words[$counter]}' in $argument_flags) true ;; *) false ;; esac"; then
(( counter++ ))
# eat "=" in case of --option=arg syntax
@ -569,10 +584,10 @@ __docker_value_of_option() {
local option_extglob=$(__docker_to_extglob "$1")
local counter=$((command_pos + 1))
while [ $counter -lt $cword ]; do
while [ "$counter" -lt "$cword" ]; do
case ${words[$counter]} in
$option_extglob )
echo ${words[$counter + 1]}
echo "${words[$counter + 1]}"
break
;;
esac
@ -609,14 +624,14 @@ __docker_to_extglob() {
__docker_subcommands() {
local subcommands="$1"
local counter=$(($command_pos + 1))
while [ $counter -lt $cword ]; do
local counter=$((command_pos + 1))
while [ "$counter" -lt "$cword" ]; do
case "${words[$counter]}" in
$(__docker_to_extglob "$subcommands") )
subcommand_pos=$counter
local subcommand=${words[$counter]}
local completions_func=_docker_${command}_${subcommand//-/_}
declare -F $completions_func >/dev/null && $completions_func
declare -F "$completions_func" >/dev/null && "$completions_func"
return 0
;;
esac
@ -951,7 +966,7 @@ __docker_complete_signals() {
SIGUSR1
SIGUSR2
)
COMPREPLY=( $( compgen -W "${signals[*]} ${signals[*]#SIG}" -- "$( echo $cur | tr '[:lower:]' '[:upper:]')" ) )
COMPREPLY=( $( compgen -W "${signals[*]} ${signals[*]#SIG}" -- "$( echo "$cur" | tr '[:lower:]' '[:upper:]')" ) )
}
__docker_complete_user_group() {
@ -991,7 +1006,7 @@ _docker_docker() {
;;
*)
local counter=$( __docker_pos_first_nonflag "$(__docker_to_extglob "$global_options_with_args")" )
if [ $cword -eq $counter ]; then
if [ "$cword" -eq "$counter" ]; then
__docker_daemon_is_experimental && commands+=(${experimental_commands[*]})
COMPREPLY=( $( compgen -W "${commands[*]} help" -- "$cur" ) )
fi
@ -1044,7 +1059,7 @@ _docker_checkpoint_create() {
;;
*)
local counter=$(__docker_pos_first_nonflag '--checkpoint-dir')
if [ $cword -eq $counter ]; then
if [ "$cword" -eq "$counter" ]; then
__docker_complete_containers_running
fi
;;
@ -1065,7 +1080,7 @@ _docker_checkpoint_ls() {
;;
*)
local counter=$(__docker_pos_first_nonflag '--checkpoint-dir')
if [ $cword -eq $counter ]; then
if [ "$cword" -eq "$counter" ]; then
__docker_complete_containers_all
fi
;;
@ -1086,9 +1101,9 @@ _docker_checkpoint_rm() {
;;
*)
local counter=$(__docker_pos_first_nonflag '--checkpoint-dir')
if [ $cword -eq $counter ]; then
if [ "$cword" -eq "$counter" ]; then
__docker_complete_containers_all
elif [ $cword -eq $(($counter + 1)) ]; then
elif [ "$cword" -eq "$((counter + 1))" ]; then
COMPREPLY=( $( compgen -W "$(__docker_q checkpoint ls "$prev" | sed 1d)" -- "$cur" ) )
fi
;;
@ -1149,7 +1164,7 @@ _docker_container_attach() {
;;
*)
local counter=$(__docker_pos_first_nonflag '--detach-keys')
if [ $cword -eq $counter ]; then
if [ "$cword" -eq "$counter" ]; then
__docker_complete_containers_running
fi
;;
@ -1170,13 +1185,13 @@ _docker_container_commit() {
*)
local counter=$(__docker_pos_first_nonflag '--author|-a|--change|-c|--message|-m')
if [ $cword -eq $counter ]; then
if [ "$cword" -eq "$counter" ]; then
__docker_complete_containers_all
return
fi
(( counter++ ))
if [ $cword -eq $counter ]; then
if [ "$cword" -eq "$counter" ]; then
__docker_complete_image_repos_and_tags
return
fi
@ -1191,7 +1206,7 @@ _docker_container_cp() {
;;
*)
local counter=$(__docker_pos_first_nonflag)
if [ $cword -eq $counter ]; then
if [ "$cword" -eq "$counter" ]; then
case "$cur" in
*:)
return
@ -1206,6 +1221,7 @@ _docker_container_cp() {
local containers=( ${COMPREPLY[@]} )
COMPREPLY=( $( compgen -W "${files[*]} ${containers[*]}" -- "$cur" ) )
# shellcheck disable=SC2128
if [[ "$COMPREPLY" == *: ]]; then
__docker_nospace
fi
@ -1215,7 +1231,7 @@ _docker_container_cp() {
fi
(( counter++ ))
if [ $cword -eq $counter ]; then
if [ "$cword" -eq "$counter" ]; then
if [ -e "$prev" ]; then
__docker_complete_containers_all
COMPREPLY=( $( compgen -W "${COMPREPLY[*]}" -S ':' ) )
@ -1240,7 +1256,7 @@ _docker_container_diff() {
;;
*)
local counter=$(__docker_pos_first_nonflag)
if [ $cword -eq $counter ]; then
if [ "$cword" -eq "$counter" ]; then
__docker_complete_containers_all
fi
;;
@ -1252,7 +1268,7 @@ _docker_container_exec() {
case "$prev" in
--env|-e)
# we do not append a "=" here because "-e VARNAME" is legal systax, too
# we do not append a "=" here because "-e VARNAME" is legal syntax, too
COMPREPLY=( $( compgen -e -- "$cur" ) )
__docker_nospace
return
@ -1287,7 +1303,7 @@ _docker_container_export() {
;;
*)
local counter=$(__docker_pos_first_nonflag)
if [ $cword -eq $counter ]; then
if [ "$cword" -eq "$counter" ]; then
__docker_complete_containers_all
fi
;;
@ -1329,7 +1345,7 @@ _docker_container_logs() {
;;
*)
local counter=$(__docker_pos_first_nonflag '--since|--tail')
if [ $cword -eq $counter ]; then
if [ "$cword" -eq "$counter" ]; then
__docker_complete_containers_all
fi
;;
@ -1425,7 +1441,7 @@ _docker_container_port() {
;;
*)
local counter=$(__docker_pos_first_nonflag)
if [ $cword -eq $counter ]; then
if [ "$cword" -eq "$counter" ]; then
__docker_complete_containers_all
fi
;;
@ -1459,7 +1475,7 @@ _docker_container_rename() {
;;
*)
local counter=$(__docker_pos_first_nonflag)
if [ $cword -eq $counter ]; then
if [ "$cword" -eq "$counter" ]; then
__docker_complete_containers_all
fi
;;
@ -1608,7 +1624,7 @@ _docker_container_run_and_create() {
--tty -t
"
if [ "$command" = "run" -o "$subcommand" = "run" ] ; then
if [ "$command" = "run" ] || [ "$subcommand" = "run" ] ; then
options_with_args="$options_with_args
--detach-keys
"
@ -1686,7 +1702,7 @@ _docker_container_run_and_create() {
return
;;
--env|-e)
# we do not append a "=" here because "-e VARNAME" is legal systax, too
# we do not append a "=" here because "-e VARNAME" is legal syntax, too
COMPREPLY=( $( compgen -e -- "$cur" ) )
__docker_nospace
return
@ -1699,6 +1715,7 @@ _docker_container_run_and_create() {
;;
*)
COMPREPLY=( $( compgen -W 'host container:' -- "$cur" ) )
# shellcheck disable=SC2128
if [ "$COMPREPLY" = "container:" ]; then
__docker_nospace
fi
@ -1753,6 +1770,7 @@ _docker_container_run_and_create() {
;;
*)
COMPREPLY=( $( compgen -W 'host container:' -- "$cur" ) )
# shellcheck disable=SC2128
if [ "$COMPREPLY" = "container:" ]; then
__docker_nospace
fi
@ -1806,8 +1824,8 @@ _docker_container_run_and_create() {
COMPREPLY=( $( compgen -W "$all_options" -- "$cur" ) )
;;
*)
local counter=$( __docker_pos_first_nonflag $( __docker_to_alternatives "$options_with_args" ) )
if [ $cword -eq $counter ]; then
local counter=$( __docker_pos_first_nonflag "$( __docker_to_alternatives "$options_with_args" )" )
if [ "$cword" -eq "$counter" ]; then
__docker_complete_images
fi
;;
@ -1816,7 +1834,7 @@ _docker_container_run_and_create() {
_docker_container_start() {
__docker_complete_detach_keys && return
# shellcheck disable=SC2078
case "$prev" in
--checkpoint)
if [ __docker_daemon_is_experimental ] ; then
@ -1884,7 +1902,7 @@ _docker_container_top() {
;;
*)
local counter=$(__docker_pos_first_nonflag)
if [ $cword -eq $counter ]; then
if [ "$cword" -eq "$counter" ]; then
__docker_complete_containers_running
fi
;;
@ -1898,7 +1916,7 @@ _docker_container_unpause() {
;;
*)
local counter=$(__docker_pos_first_nonflag)
if [ $cword -eq $counter ]; then
if [ "$cword" -eq "$counter" ]; then
__docker_complete_containers_unpauseable
fi
;;
@ -2098,7 +2116,7 @@ _docker_daemon() {
return
;;
--storage-driver|-s)
COMPREPLY=( $( compgen -W "aufs btrfs devicemapper overlay overlay2 vfs zfs" -- "$(echo $cur | tr '[:upper:]' '[:lower:]')" ) )
COMPREPLY=( $( compgen -W "aufs btrfs devicemapper overlay overlay2 vfs zfs" -- "$(echo "$cur" | tr '[:upper:]' '[:lower:]')" ) )
return
;;
--storage-opt)
@ -2211,7 +2229,7 @@ _docker_export() {
_docker_help() {
local counter=$(__docker_pos_first_nonflag)
if [ $cword -eq $counter ]; then
if [ "$cword" -eq "$counter" ]; then
COMPREPLY=( $( compgen -W "${commands[*]}" -- "$cur" ) )
fi
}
@ -2348,8 +2366,8 @@ _docker_image_build() {
COMPREPLY=( $( compgen -W "$all_options" -- "$cur" ) )
;;
*)
local counter=$( __docker_pos_first_nonflag $( __docker_to_alternatives "$options_with_args" ) )
if [ $cword -eq $counter ]; then
local counter=$( __docker_pos_first_nonflag "$( __docker_to_alternatives "$options_with_args" )" )
if [ "$cword" -eq "$counter" ]; then
_filedir -d
fi
;;
@ -2369,7 +2387,7 @@ _docker_image_history() {
;;
*)
local counter=$(__docker_pos_first_nonflag)
if [ $cword -eq $counter ]; then
if [ "$cword" -eq "$counter" ]; then
__docker_complete_images
fi
;;
@ -2393,12 +2411,12 @@ _docker_image_import() {
;;
*)
local counter=$(__docker_pos_first_nonflag '--change|-c|--message|-m')
if [ $cword -eq $counter ]; then
if [ "$cword" -eq "$counter" ]; then
return
fi
(( counter++ ))
if [ $cword -eq $counter ]; then
if [ "$cword" -eq "$counter" ]; then
__docker_complete_image_repos_and_tags
return
fi
@ -2493,7 +2511,7 @@ _docker_image_pull() {
;;
*)
local counter=$(__docker_pos_first_nonflag)
if [ $cword -eq $counter ]; then
if [ "$cword" -eq "$counter" ]; then
for arg in "${COMP_WORDS[@]}"; do
case "$arg" in
--all-tags|-a)
@ -2515,7 +2533,7 @@ _docker_image_push() {
;;
*)
local counter=$(__docker_pos_first_nonflag)
if [ $cword -eq $counter ]; then
if [ "$cword" -eq "$counter" ]; then
__docker_complete_image_repos_and_tags
fi
;;
@ -2567,13 +2585,13 @@ _docker_image_tag() {
*)
local counter=$(__docker_pos_first_nonflag)
if [ $cword -eq $counter ]; then
if [ "$cword" -eq "$counter" ]; then
__docker_complete_image_repos_and_tags
return
fi
(( counter++ ))
if [ $cword -eq $counter ]; then
if [ "$cword" -eq "$counter" ]; then
__docker_complete_image_repos_and_tags
return
fi
@ -2737,10 +2755,10 @@ _docker_network_connect() {
COMPREPLY=( $( compgen -W "$boolean_options $options_with_args" -- "$cur" ) )
;;
*)
local counter=$( __docker_pos_first_nonflag $( __docker_to_alternatives "$options_with_args" ) )
if [ $cword -eq $counter ]; then
local counter=$( __docker_pos_first_nonflag "$( __docker_to_alternatives "$options_with_args" )" )
if [ "$cword" -eq "$counter" ]; then
__docker_complete_networks
elif [ $cword -eq $(($counter + 1)) ]; then
elif [ "$cword" -eq "$((counter + 1))" ]; then
__docker_complete_containers_all
fi
;;
@ -2788,9 +2806,9 @@ _docker_network_disconnect() {
;;
*)
local counter=$(__docker_pos_first_nonflag)
if [ $cword -eq $counter ]; then
if [ "$cword" -eq "$counter" ]; then
__docker_complete_networks
elif [ $cword -eq $(($counter + 1)) ]; then
elif [ "$cword" -eq "$((counter + 1))" ]; then
__docker_complete_containers_in_network "$prev"
fi
;;
@ -2969,7 +2987,7 @@ _docker_service_logs() {
;;
*)
local counter=$(__docker_pos_first_nonflag '--since|--tail')
if [ $cword -eq $counter ]; then
if [ "$cword" -eq "$counter" ]; then
__docker_complete_services_and_tasks
fi
;;
@ -3076,7 +3094,7 @@ _docker_service_ps() {
;;
*)
local counter=$(__docker_pos_first_nonflag '--filter|-f')
if [ $cword -eq $counter ]; then
if [ "$cword" -eq "$counter" ]; then
__docker_complete_services
fi
;;
@ -3091,6 +3109,7 @@ _docker_service_update() {
# and `docker service update`
_docker_service_update_and_create() {
local options_with_args="
--credential-spec
--endpoint-mode
--entrypoint
--env -e
@ -3321,13 +3340,13 @@ _docker_service_update_and_create() {
COMPREPLY=( $( compgen -W "$boolean_options $options_with_args" -- "$cur" ) )
;;
*)
local counter=$( __docker_pos_first_nonflag $( __docker_to_alternatives "$options_with_args" ) )
local counter=$( __docker_pos_first_nonflag "$( __docker_to_alternatives "$options_with_args" )" )
if [ "$subcommand" = "update" ] ; then
if [ $cword -eq $counter ]; then
if [ "$cword" -eq "$counter" ]; then
__docker_complete_services
fi
else
if [ $cword -eq $counter ]; then
if [ "$cword" -eq "$counter" ]; then
__docker_complete_images
fi
fi
@ -3466,7 +3485,7 @@ _docker_swarm_join_token() {
;;
*)
local counter=$( __docker_pos_first_nonflag )
if [ $cword -eq $counter ]; then
if [ "$cword" -eq "$counter" ]; then
COMPREPLY=( $( compgen -W "manager worker" -- "$cur" ) )
fi
;;
@ -3685,7 +3704,7 @@ _docker_node_update() {
;;
*)
local counter=$(__docker_pos_first_nonflag '--availability|--label-add|--label-rm|--role')
if [ $cword -eq $counter ]; then
if [ "$cword" -eq "$counter" ]; then
__docker_complete_nodes
fi
;;
@ -3732,10 +3751,10 @@ _docker_plugin_create() {
;;
*)
local counter=$(__docker_pos_first_nonflag)
if [ $cword -eq $counter ]; then
if [ "$cword" -eq "$counter" ]; then
# reponame
return
elif [ $cword -eq $((counter + 1)) ]; then
elif [ "$cword" -eq "$((counter + 1))" ]; then
_filedir -d
fi
;;
@ -3749,7 +3768,7 @@ _docker_plugin_disable() {
;;
*)
local counter=$(__docker_pos_first_nonflag)
if [ $cword -eq $counter ]; then
if [ "$cword" -eq "$counter" ]; then
__docker_complete_plugins_installed --filter enabled=true
fi
;;
@ -3769,7 +3788,7 @@ _docker_plugin_enable() {
;;
*)
local counter=$(__docker_pos_first_nonflag '--timeout')
if [ $cword -eq $counter ]; then
if [ "$cword" -eq "$counter" ]; then
__docker_complete_plugins_installed --filter enabled=false
fi
;;
@ -3849,7 +3868,7 @@ _docker_plugin_push() {
;;
*)
local counter=$(__docker_pos_first_nonflag)
if [ $cword -eq $counter ]; then
if [ "$cword" -eq "$counter" ]; then
__docker_complete_plugins_installed
fi
;;
@ -3878,7 +3897,7 @@ _docker_plugin_set() {
;;
*)
local counter=$(__docker_pos_first_nonflag)
if [ $cword -eq $counter ]; then
if [ "$cword" -eq "$counter" ]; then
__docker_complete_plugins_installed
fi
;;
@ -3892,10 +3911,10 @@ _docker_plugin_upgrade() {
;;
*)
local counter=$(__docker_pos_first_nonflag)
if [ $cword -eq $counter ]; then
if [ "$cword" -eq "$counter" ]; then
__docker_complete_plugins_installed
__ltrim_colon_completions "$cur"
elif [ $cword -eq $((counter + 1)) ]; then
elif [ "$cword" -eq "$((counter + 1))" ]; then
local plugin_images="$(__docker_plugins_installed)"
COMPREPLY=( $(compgen -S : -W "${plugin_images%:*}" -- "$cur") )
__docker_nospace
@ -4189,7 +4208,7 @@ _docker_stack_ps() {
;;
*)
local counter=$(__docker_pos_first_nonflag '--filter|-f')
if [ $cword -eq $counter ]; then
if [ "$cword" -eq "$counter" ]; then
__docker_complete_stacks
fi
;;
@ -4243,7 +4262,7 @@ _docker_stack_services() {
;;
*)
local counter=$(__docker_pos_first_nonflag '--filter|-f|--format')
if [ $cword -eq $counter ]; then
if [ "$cword" -eq "$counter" ]; then
__docker_complete_stacks
fi
;;
@ -4685,7 +4704,7 @@ _docker() {
local command='docker' command_pos=0 subcommand_pos
local counter=1
while [ $counter -lt $cword ]; do
while [ "$counter" -lt "$cword" ]; do
case "${words[$counter]}" in
# save host so that completion can use custom daemon
--host|-H)

View File

@ -7,6 +7,7 @@
DEV_DOCKER_IMAGE_NAME = docker-cli-dev
LINTER_IMAGE_NAME = docker-cli-lint
CROSS_IMAGE_NAME = docker-cli-cross
VALIDATE_IMAGE_NAME = docker-cli-shell-validate
MOUNTS = -v "$(CURDIR)":/go/src/github.com/docker/cli
VERSION = $(shell cat VERSION)
ENVVARS = -e VERSION=$(VERSION) -e GITCOMMIT
@ -25,6 +26,9 @@ build_linter_image:
build_cross_image:
docker build ${DOCKER_BUILD_ARGS} -t $(CROSS_IMAGE_NAME) -f ./dockerfiles/Dockerfile.cross .
.PHONY: build_shell_validate_image
build_shell_validate_image:
docker build -t $(VALIDATE_IMAGE_NAME) -f ./dockerfiles/Dockerfile.shellcheck .
# build executable using a container
binary: build_docker_image
@ -80,3 +84,7 @@ manpages: build_docker_image
.PHONY: yamldocs
yamldocs: build_docker_image
docker run -ti --rm $(MOUNTS) $(DEV_DOCKER_IMAGE_NAME) make yamldocs
.PHONY: shellcheck
shellcheck: build_shell_validate_image
docker run -ti --rm $(MOUNTS) $(VALIDATE_IMAGE_NAME) make shellcheck

View File

@ -4,21 +4,21 @@ FROM golang:1.8.3-alpine
RUN apk add -U git make bash coreutils
ARG VNDR_SHA=9909bb2b8a0b7ea464527b376dc50389c90df587
RUN go get github.com/LK4D4/vndr && \
RUN go get -d github.com/LK4D4/vndr && \
cd /go/src/github.com/LK4D4/vndr && \
git checkout -q "$VNDR_SHA" && \
go build -v -o /usr/bin/vndr . && \
rm -rf /go/src/* /go/pkg/* /go/bin/*
ARG BINDATA_SHA=a0ff2567cfb70903282db057e799fd826784d41d
RUN go get github.com/jteeuwen/go-bindata/go-bindata && \
RUN go get -d github.com/jteeuwen/go-bindata/go-bindata && \
cd /go/src/github.com/jteeuwen/go-bindata/go-bindata && \
git checkout -q "$BINDATA_SHA" && \
go build -v -o /usr/bin/go-bindata . && \
rm -rf /go/src/* /go/pkg/* /go/bin/*
ARG FILEWATCHER_SHA=2e12ea42f6c8c089b19e992145bb94e8adaecedb
RUN go get github.com/dnephin/filewatcher && \
RUN go get -d github.com/dnephin/filewatcher && \
cd /go/src/github.com/dnephin/filewatcher && \
git checkout -q "$FILEWATCHER_SHA" && \
go build -v -o /usr/bin/filewatcher . && \

View File

@ -3,7 +3,7 @@ FROM golang:1.8.3-alpine
RUN apk add -U git
ARG GOMETALINTER_SHA=4306381615a2ba2a207f8fcea02c08c6b2b0803f
RUN go get github.com/alecthomas/gometalinter && \
RUN go get -d github.com/alecthomas/gometalinter && \
cd /go/src/github.com/alecthomas/gometalinter && \
git checkout -q "$GOMETALINTER_SHA" && \
go build -v -o /usr/local/bin/gometalinter . && \

View File

@ -0,0 +1,9 @@
FROM debian:stretch-slim
RUN apt-get update && \
apt-get -y install make shellcheck && \
apt-get clean
WORKDIR /go/src/github.com/docker/cli
CMD bash

View File

@ -530,15 +530,16 @@ FROM extras:${CODE_VERSION}
CMD /code/run-extras
```
To use the default value of an `ARG` declared before the first `FROM` use an
`ARG` instruction without a value:
An `ARG` declared before a `FROM` is outside of a build stage, so it
can't be used in any instruction after a `FROM`. To use the default value of
an `ARG` declared before the first `FROM` use an `ARG` instruction without
a value inside of a build stage:
```Dockerfile
ARG SETTINGS=default
FROM busybox
ARG SETTINGS
ARG VERSION=latest
FROM busybox:$VERSION
ARG VERSION
RUN echo $VERSION > image_version
```
## RUN
@ -1364,8 +1365,8 @@ defined in the Dockerfile, the build outputs a warning.
[Warning] One or more build-args [foo] were not consumed.
```
The Dockerfile author can define a single variable by specifying `ARG` once or many
variables by specifying `ARG` more than once. For example, a valid Dockerfile:
A Dockerfile may include one or more `ARG` instructions. For example,
the following is a valid Dockerfile:
```
FROM busybox
@ -1374,7 +1375,13 @@ ARG buildno
...
```
A Dockerfile author may optionally specify a default value for an `ARG` instruction:
> **Warning:** It is not recommended to use build-time variables for
> passing secrets like github keys, user credentials etc. Build-time variable
> values are visible to any user of the image with the `docker history` command.
### Default values
An `ARG` instruction can optionally include a default value:
```
FROM busybox
@ -1383,8 +1390,10 @@ ARG buildno=1
...
```
If an `ARG` value has a default and if there is no value passed at build-time, the
builder uses the default.
If an `ARG` instruction has a default value and if there is no value passed
at build-time, the builder uses the default.
### Scope
An `ARG` variable definition comes into effect from the line on which it is
defined in the `Dockerfile` not from the argument's use on the command-line or
@ -1408,9 +1417,21 @@ subsequent line 3. The `USER` at line 4 evaluates to `what_user` as `user` is
defined and the `what_user` value was passed on the command line. Prior to its definition by an
`ARG` instruction, any use of a variable results in an empty string.
> **Warning:** It is not recommended to use build-time variables for
> passing secrets like github keys, user credentials etc. Build-time variable
> values are visible to any user of the image with the `docker history` command.
An `ARG` instruction goes out of scope at the end of the build
stage where it was defined. To use an arg in multiple stages, each stage must
include the `ARG` instruction.
```
FROM busybox
ARG SETTINGS
RUN ./run/setup $SETTINGS
FROM busybox
ARG SETTINGS
RUN ./run/other $SETTINGS
```
### Using ARG variables
You can use an `ARG` or an `ENV` instruction to specify variables that are
available to the `RUN` instruction. Environment variables defined using the
@ -1459,6 +1480,8 @@ from the command line and persist them in the final image by leveraging the
`ENV` instruction. Variable expansion is only supported for [a limited set of
Dockerfile instructions.](#environment-replacement)
### Predefined ARGs
Docker has a set of predefined `ARG` variables that you can use without a
corresponding `ARG` instruction in the Dockerfile.

View File

@ -80,9 +80,9 @@ Docker images report the following events:
Docker plugins report the following events:
- `install`
- `enable`
- `disable`
- `install`
- `remove`
#### Volumes
@ -90,9 +90,9 @@ Docker plugins report the following events:
Docker volumes report the following events:
- `create`
- `destroy`
- `mount`
- `unmount`
- `destroy`
#### Networks
@ -100,8 +100,9 @@ Docker networks report the following events:
- `create`
- `connect`
- `disconnect`
- `destroy`
- `disconnect`
- `remove`
#### Daemons
@ -109,6 +110,38 @@ Docker daemons report the following events:
- `reload`
#### Services
Docker services report the following events:
- `create`
- `remove`
- `update`
#### Nodes
Docker nodes report the following events:
- `create`
- `remove`
- `update`
#### Secrets
Docker secrets report the following events:
- `create`
- `remove`
- `update`
#### Configs
Docker configs report the following events:
- `create`
- `remove`
- `update`
### Limiting, filtering, and formatting the output
#### Limit events by time
@ -149,7 +182,8 @@ The currently supported filters are:
* label (`label=<key>` or `label=<key>=<value>`)
* network (`network=<name or id>`)
* plugin (`plugin=<name or id>`)
* type (`type=<container or image or volume or network or daemon or plugin>`)
* scope (`scope=<local or swarm>`)
* type (`type=<container or image or volume or network or daemon or plugin or service or node or secret or config>`)
* volume (`volume=<name or id>`)
#### Format
@ -317,6 +351,29 @@ $ docker events --filter 'type=plugin'
2016-07-25T17:30:14.825557616Z plugin pull ec7b87f2ce84330fe076e666f17dfc049d2d7ae0b8190763de94e1f2d105993f (name=tiborvass/sample-volume-plugin:latest)
2016-07-25T17:30:14.888127370Z plugin enable ec7b87f2ce84330fe076e666f17dfc049d2d7ae0b8190763de94e1f2d105993f (name=tiborvass/sample-volume-plugin:latest)
$ docker events -f type=service
2017-07-12T06:34:07.999446625Z service create wj64st89fzgchxnhiqpn8p4oj (name=reverent_albattani)
2017-07-12T06:34:21.405496207Z service remove wj64st89fzgchxnhiqpn8p4oj (name=reverent_albattani)
$ docker events -f type=node
2017-07-12T06:21:51.951586759Z node update 3xyz5ttp1a253q74z1thwywk9 (name=ip-172-31-23-42, state.new=ready, state.old=unknown)
$ docker events -f type=secret
2017-07-12T06:32:13.915704367Z secret create s8o6tmlnndrgzbmdilyy5ymju (name=new_secret)
2017-07-12T06:32:37.052647783Z secret remove s8o6tmlnndrgzbmdilyy5ymju (name=new_secret)
$ docker events -f type=config
2017-07-12T06:44:13.349037127Z config create u96zlvzdfsyb9sg4mhyxfh3rl (name=abc)
2017-07-12T06:44:36.327694184Z config remove u96zlvzdfsyb9sg4mhyxfh3rl (name=abc)
$ docker events --filter 'scope=swarm'
2017-07-10T07:46:50.250024503Z service create m8qcxu8081woyof7w3jaax6gk (name=affectionate_wilson)
2017-07-10T07:47:31.093797134Z secret create 6g5pufzsv438p9tbvl9j94od4 (name=new_secret)
```
### Format the output

View File

@ -94,7 +94,6 @@ radial/busyboxplus Full-chain, Internet enabled, busybox made from scratch. Co
The flag `--limit` is the maximum number of results returned by a search. This value could
be in the range between 1 and 100. The default value of `--limit` is 25.
### Filtering
The filtering flag (`-f` or `--filter`) format is a `key=value` pair. If there is more
@ -103,9 +102,8 @@ than one filter, then pass multiple flags (e.g. `--filter "foo=bar" --filter "bi
The currently supported filters are:
* stars (int - number of stars the image has)
* is-automated (true|false) - is the image automated or not
* is-official (true|false) - is the image official or not
* is-automated (boolean - true or false) - is the image automated or not
* is-official (boolean - true or false) - is the image official or not
#### stars
@ -121,7 +119,6 @@ progrium/busybox 50
radial/busyboxplus Full-chain, Internet enabled, busybox made... 8 [OK]
```
#### is-automated
This example displays images with a name containing 'busybox'

View File

@ -23,6 +23,7 @@ Create a new service
Options:
--constraint list Placement constraints
--container-label list Container labels
--credential-spec Credential spec for managed service account (Windows only)
-d, --detach Exit immediately instead of waiting for the service to converge (default true)
--dns list Set custom DNS servers
--dns-option list Set DNS options
@ -779,6 +780,24 @@ $ docker service create --name dns-cache -p 53:53/tcp -p 53:53/udp dns-cache
$ docker service create --name dns-cache -p 53:53/udp dns-cache
```
### Provide credential specs for managed service accounts (Windows only)
This option is only used for services using Windows containers. The
`--credential-spec` must be in the format `file://<filename>` or
`registry://<value-name>`.
When using the `file://<filename>` format, the referenced file must be
present in the `CredentialSpecs` subdirectory in the docker data directory,
which defaults to `C:\ProgramData\Docker\` on Windows. For example,
specifying `file://spec.json` loads `C:\ProgramData\Docker\CredentialSpecs\spec.json`.
When using the `registry://<value-name>` format, the credential spec is
read from the Windows registry on the daemon's host. The specified
registry value must be located in:
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\Containers\CredentialSpecs
### Create services using templates
You can use templates for some flags of `service create`, using the syntax

View File

@ -26,6 +26,7 @@ Options:
--constraint-rm list Remove a constraint
--container-label-add list Add or update a container label
--container-label-rm list Remove a container label by its key
--credential-spec Credential spec for managed service account (Windows only)
-d, --detach Exit immediately instead of waiting for the service to converge (default true)
--dns-add list Add or update a custom DNS server
--dns-option-add list Add or update a DNS option

View File

@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -e
# Maintain an array of files to shellcheck not the best solution but will do for the time being
FILES=()
FILES+=("contrib/completion/bash/docker")
FILES+=("scripts/validate/shellcheck")
for f in "${FILES[@]}"; do
shellcheck "$f"
done

View File

@ -52,3 +52,4 @@ gopkg.in/yaml.v2 4c78c975fe7c825c6d1466c42be594d1d6f3aba6
github.com/tonistiigi/fsutil 0ac4c11b053b9c5c7c47558f81f96c7100ce50fb
github.com/stevvooe/continuity cd7a8e21e2b6f84799f5dd4b65faf49c8d3ee02d
golang.org/x/sync de49d9dcd27d4f764488181bea099dfe6179bcf0
vbom.ml/util 928aaa586d7718c70f4090ddf83f2b34c16fdc8d

View File

@ -0,0 +1,17 @@
The MIT License (MIT)
Copyright (c) 2015 Frits van Bommel
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.

View File

@ -0,0 +1,5 @@
## util [![GoDoc](https://godoc.org/vbom.ml/util?status.svg)](https://godoc.org/vbom.ml/util)
import "vbom.ml/util"
Go utility packages.

View File

@ -0,0 +1,5 @@
## sortorder [![GoDoc](https://godoc.org/vbom.ml/util/sortorder?status.svg)](https://godoc.org/vbom.ml/util/sortorder)
import "vbom.ml/util/sortorder"
Sort orders and comparison functions.

View File

@ -0,0 +1,5 @@
// Package sortorder implements sort orders and comparison functions.
//
// Currently, it only implements so-called "natural order", where integers
// embedded in strings are compared by value.
package sortorder // import "vbom.ml/util/sortorder"

View File

@ -0,0 +1,76 @@
package sortorder
// Natural implements sort.Interface to sort strings in natural order. This
// means that e.g. "abc2" < "abc12".
//
// Non-digit sequences and numbers are compared separately. The former are
// compared bytewise, while the latter are compared numerically (except that
// the number of leading zeros is used as a tie-breaker, so e.g. "2" < "02")
//
// Limitation: only ASCII digits (0-9) are considered.
type Natural []string
func (n Natural) Len() int { return len(n) }
func (n Natural) Swap(i, j int) { n[i], n[j] = n[j], n[i] }
func (n Natural) Less(i, j int) bool { return NaturalLess(n[i], n[j]) }
func isdigit(b byte) bool { return '0' <= b && b <= '9' }
// NaturalLess compares two strings using natural ordering. This means that e.g.
// "abc2" < "abc12".
//
// Non-digit sequences and numbers are compared separately. The former are
// compared bytewise, while the latter are compared numerically (except that
// the number of leading zeros is used as a tie-breaker, so e.g. "2" < "02")
//
// Limitation: only ASCII digits (0-9) are considered.
func NaturalLess(str1, str2 string) bool {
idx1, idx2 := 0, 0
for idx1 < len(str1) && idx2 < len(str2) {
c1, c2 := str1[idx1], str2[idx2]
dig1, dig2 := isdigit(c1), isdigit(c2)
switch {
case dig1 != dig2: // Digits before other characters.
return dig1 // True if LHS is a digit, false if the RHS is one.
case !dig1: // && !dig2, because dig1 == dig2
// UTF-8 compares bytewise-lexicographically, no need to decode
// codepoints.
if c1 != c2 {
return c1 < c2
}
idx1++
idx2++
default: // Digits
// Eat zeros.
for ; idx1 < len(str1) && str1[idx1] == '0'; idx1++ {
}
for ; idx2 < len(str2) && str2[idx2] == '0'; idx2++ {
}
// Eat all digits.
nonZero1, nonZero2 := idx1, idx2
for ; idx1 < len(str1) && isdigit(str1[idx1]); idx1++ {
}
for ; idx2 < len(str2) && isdigit(str2[idx2]); idx2++ {
}
// If lengths of numbers with non-zero prefix differ, the shorter
// one is less.
if len1, len2 := idx1-nonZero1, idx2-nonZero2; len1 != len2 {
return len1 < len2
}
// If they're not equal, string comparison is correct.
if nr1, nr2 := str1[nonZero1:idx1], str2[nonZero2:idx2]; nr1 != nr2 {
return nr1 < nr2
}
// Otherwise, the one with less zeros is less.
// Because everything up to the number is equal, comparing the index
// after the zeros is sufficient.
if nonZero1 != nonZero2 {
return nonZero1 < nonZero2
}
}
// They're identical so far, so continue comparing.
}
// So far they are identical. At least one is ended. If the other continues,
// it sorts last.
return len(str1) < len(str2)
}