Compare commits

..

15 Commits

Author SHA1 Message Date
Ettore Di Giacinto
98b01ce00b Tag 0.11.2 2021-02-22 14:46:36 +01:00
Ettore Di Giacinto
749a4cb615 Add --backend-args
Allow to add arguments to the backend build arguments

Fixes #146
2021-02-22 13:49:29 +01:00
Ettore Di Giacinto
57e19c61e7 Start spinner before pulling docker artifacts 2021-02-22 11:54:09 +01:00
Ettore Di Giacinto
5ee1e28b9c Display informative message when pulling docker artifacts 2021-02-22 11:50:52 +01:00
Ettore Di Giacinto
21bd76af9c Uncomplicate runCommand and return command output
Fixes #189
2021-02-22 11:44:46 +01:00
Ettore Di Giacinto
89bd7c2281 Tag 0.11.1 2021-02-17 13:58:21 +01:00
Ettore Di Giacinto
49d7efa9ea Update vendor 2021-02-17 13:01:36 +01:00
Ettore Di Giacinto
b3e3abec8f Fixup spinner data race
Add spinner lock
2021-02-17 13:01:32 +01:00
Ettore Di Giacinto
92e73051a0 Initialize cached in singleton 2021-02-17 09:34:54 +01:00
Ettore Di Giacinto
fd7405c2cc Add inmemory DB integration test 2021-02-15 18:53:56 +01:00
Ettore Di Giacinto
2448f3175e Initialize cache db if empty 2021-02-15 18:53:37 +01:00
Ettore Di Giacinto
101df40eec Merge pull request #183 from mudler/docker-debug-output
Add realtime output for building phase
2021-02-13 12:02:20 +01:00
Daniele Rondina
c22adb3a47 compiler: Move spinner at the low level 2021-02-13 09:28:54 +01:00
Daniele Rondina
c1fe3278fa backend: Add realtime output on building phase
The realtime output could be configured through
LUET_GENERAL__SHOW_BUILD_OUTPUT environment
variable or related config option or through
`--live-output` option.
2021-02-02 12:58:34 +01:00
Daniele Rondina
2854c68209 logger: Add ln option for writer log 2021-02-01 19:10:05 +01:00
19 changed files with 404 additions and 163 deletions

View File

@@ -72,6 +72,7 @@ Build packages specifying multiple definition trees:
viper.BindPFlag("nodeps", cmd.Flags().Lookup("nodeps"))
viper.BindPFlag("onlydeps", cmd.Flags().Lookup("onlydeps"))
viper.BindPFlag("values", cmd.Flags().Lookup("values"))
viper.BindPFlag("backend-args", cmd.Flags().Lookup("backend-args"))
viper.BindPFlag("image-repository", cmd.Flags().Lookup("image-repository"))
viper.BindPFlag("push", cmd.Flags().Lookup("push"))
@@ -83,6 +84,9 @@ Build packages specifying multiple definition trees:
LuetCfg.Viper.BindPFlag("solver.discount", cmd.Flags().Lookup("solver-discount"))
LuetCfg.Viper.BindPFlag("solver.rate", cmd.Flags().Lookup("solver-rate"))
LuetCfg.Viper.BindPFlag("solver.max_attempts", cmd.Flags().Lookup("solver-attempts"))
LuetCfg.Viper.BindPFlag("general.show_build_output", cmd.Flags().Lookup("live-output"))
LuetCfg.Viper.BindPFlag("backend-args", cmd.Flags().Lookup("backend-args"))
},
Run: func(cmd *cobra.Command, args []string) {
@@ -107,6 +111,7 @@ Build packages specifying multiple definition trees:
full, _ := cmd.Flags().GetBool("full")
concurrent, _ := cmd.Flags().GetBool("solver-concurrent")
var results Results
backendArgs := viper.GetStringSlice("backend-args")
out, _ := cmd.Flags().GetString("output")
if out != "terminal" {
@@ -155,6 +160,8 @@ Build packages specifying multiple definition trees:
LuetCfg.GetSolverOptions().Discount = float32(discount)
LuetCfg.GetSolverOptions().MaxAttempts = attempts
LuetCfg.GetGeneral().ShowBuildOutput = LuetCfg.Viper.GetBool("general.show_build_output")
Debug("Solver", LuetCfg.GetSolverOptions().CompactString())
opts := compiler.NewDefaultCompilerOptions()
@@ -176,6 +183,7 @@ Build packages specifying multiple definition trees:
}
luetCompiler := compiler.NewLuetCompiler(compilerBackend, generalRecipe.GetDatabase(), opts, solverOpts)
luetCompiler.SetBackendArgs(backendArgs)
luetCompiler.SetConcurrency(concurrency)
luetCompiler.SetCompressionType(compiler.CompressionImplementation(compressionType))
if full {
@@ -293,6 +301,7 @@ func init() {
if err != nil {
Fatal(err)
}
buildCmd.Flags().StringSliceP("tree", "t", []string{path}, "Path of the tree to use.")
buildCmd.Flags().String("backend", "docker", "backend used (docker,img)")
buildCmd.Flags().Bool("privileged", false, "Privileged (Keep permissions)")
@@ -301,6 +310,7 @@ func init() {
buildCmd.Flags().Bool("all", false, "Build all specfiles in the tree")
buildCmd.Flags().Bool("full", false, "Build all packages (optimized)")
buildCmd.Flags().String("values", "", "Build values file to interpolate with each package")
buildCmd.Flags().StringSliceP("backend-args", "a", []string{}, "Backend args")
buildCmd.Flags().String("destination", filepath.Join(path, "build"), "Destination folder")
buildCmd.Flags().String("compression", "none", "Compression alg: none, gzip, zstd")
@@ -317,6 +327,7 @@ func init() {
buildCmd.Flags().Float32("solver-discount", 1.0, "Solver discount rate")
buildCmd.Flags().Int("solver-attempts", 9000, "Solver maximum attempts")
buildCmd.Flags().Bool("solver-concurrent", false, "Use concurrent solver (experimental)")
buildCmd.Flags().Bool("live-output", LuetCfg.GetGeneral().ShowBuildOutput, "Enable live output of the build phase.")
buildCmd.Flags().Bool("pretend", false, "Just print what packages will be compiled")

View File

@@ -40,7 +40,7 @@ var Verbose bool
var LockedCommands = []string{"install", "uninstall", "upgrade"}
const (
LuetCLIVersion = "0.11.0"
LuetCLIVersion = "0.11.2"
LuetEnvPrefix = "LUET"
)

2
go.mod
View File

@@ -7,7 +7,7 @@ require (
github.com/Sabayon/pkgs-checker v0.7.2
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef
github.com/asdine/storm v0.0.0-20190418133842-e0f77eada154
github.com/briandowns/spinner v1.7.0
github.com/briandowns/spinner v1.12.1-0.20201220203425-e201aaea0a31
github.com/cavaliercoder/grab v1.0.1-0.20201108051000-98a5bfe305ec
github.com/containerd/containerd v1.4.1-0.20201117152358-0edc412565dc
github.com/crillab/gophersat v1.3.2-0.20201023142334-3fc2ac466765

4
go.sum
View File

@@ -144,8 +144,8 @@ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJm
github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/briandowns/spinner v1.7.0 h1:aan1hBBOoscry2TXAkgtxkJiq7Se0+9pt+TUWaPrB4g=
github.com/briandowns/spinner v1.7.0/go.mod h1://Zf9tMcxfRUA36V23M6YGEAv+kECGfvpnLTnb8n4XQ=
github.com/briandowns/spinner v1.12.1-0.20201220203425-e201aaea0a31 h1:yInAg9pE5qGec5eQ7XdfOTTaGwGxD3bKFVjmD6VKkwc=
github.com/briandowns/spinner v1.12.1-0.20201220203425-e201aaea0a31/go.mod h1:QOuQk7x+EaDASo80FEXwlwiA+j/PPIcX3FScO+3/ZPQ=
github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=

View File

@@ -16,8 +16,14 @@
package backend
import (
"github.com/google/go-containerregistry/pkg/crane"
"os/exec"
"github.com/mudler/luet/pkg/compiler"
"github.com/mudler/luet/pkg/config"
. "github.com/mudler/luet/pkg/logger"
"github.com/google/go-containerregistry/pkg/crane"
"github.com/pkg/errors"
)
const (
@@ -41,3 +47,40 @@ func NewBackend(s string) compiler.CompilerBackend {
}
return compilerBackend
}
func runCommand(cmd *exec.Cmd) error {
output := ""
buffered := !config.LuetCfg.GetGeneral().ShowBuildOutput
writer := NewBackendWriter(buffered)
cmd.Stdout = writer
cmd.Stderr = writer
if buffered {
Spinner(22)
defer SpinnerStop()
}
err := cmd.Start()
if err != nil {
return errors.Wrap(err, "Failed starting command")
}
err = cmd.Wait()
if err != nil {
output = writer.GetCombinedOutput()
return errors.Wrapf(err, "Failed running command: %s", output)
}
return nil
}
func genBuildCommand(opts compiler.CompilerBackendOptions) []string {
context := opts.Context
if context == "" {
context = "."
}
buildarg := append(opts.BackendArgs, "-f", opts.DockerFileName, "-t", opts.ImageName, context)
return append([]string{"build"}, buildarg...)
}

View File

@@ -44,28 +44,24 @@ func NewSimpleDockerBackend() compiler.CompilerBackend {
// TODO: Missing still: labels, and build args expansion
func (*SimpleDocker) BuildImage(opts compiler.CompilerBackendOptions) error {
name := opts.ImageName
path := opts.SourcePath
dockerfileName := opts.DockerFileName
context := opts.Context
if context == "" {
context = "."
}
buildarg := []string{"build", "-f", dockerfileName, "-t", name, context}
buildarg := genBuildCommand(opts)
Info(":whale2: Building image " + name)
cmd := exec.Command("docker", buildarg...)
cmd.Dir = path
out, err := cmd.CombinedOutput()
cmd.Dir = opts.SourcePath
err := runCommand(cmd)
if err != nil {
return errors.Wrap(err, "Failed building image: "+string(out))
return err
}
Info(":whale: Building image " + name + " done")
if os.Getenv("DOCKER_SQUASH") == "true" {
Info(":whale: Squashing image " + name)
var client *docker.Client
Spinner(22)
defer SpinnerStop()
client, err = docker.NewClientFromEnv()
if err != nil {
return errors.Wrap(err, "could not connect to the Docker daemon")
@@ -74,13 +70,8 @@ func (*SimpleDocker) BuildImage(opts compiler.CompilerBackendOptions) error {
if err != nil {
return errors.Wrap(err, "Failed squashing image")
}
Info(":whale: Squashing image " + name + " done")
}
if config.LuetCfg.GetGeneral().ShowBuildOutput {
Info(string(out))
} else {
Debug(string(out))
Info(":whale: Squashing image " + name + " done")
}
return nil
@@ -101,11 +92,16 @@ func (*SimpleDocker) DownloadImage(opts compiler.CompilerBackendOptions) error {
name := opts.ImageName
buildarg := []string{"pull", name}
Debug(":whale: Downloading image " + name)
Spinner(22)
defer SpinnerStop()
cmd := exec.Command("docker", buildarg...)
out, err := cmd.CombinedOutput()
if err != nil {
return errors.Wrap(err, "Failed pulling image: "+string(out))
}
Info(":whale: Downloaded image:", name)
return nil
}
@@ -142,6 +138,10 @@ func (*SimpleDocker) RemoveImage(opts compiler.CompilerBackendOptions) error {
func (*SimpleDocker) Push(opts compiler.CompilerBackendOptions) error {
name := opts.ImageName
pusharg := []string{"push", name}
Spinner(22)
defer SpinnerStop()
out, err := exec.Command("docker", pusharg...).CombinedOutput()
if err != nil {
return errors.Wrap(err, "Failed pushing image: "+string(out))
@@ -170,6 +170,10 @@ func (*SimpleDocker) ExportImage(opts compiler.CompilerBackendOptions) error {
buildarg := []string{"save", name, "-o", path}
Debug(":whale: Saving image " + name)
Spinner(22)
defer SpinnerStop()
out, err := exec.Command("docker", buildarg...).CombinedOutput()
if err != nil {
return errors.Wrap(err, "Failed exporting image: "+string(out))
@@ -194,10 +198,16 @@ func (b *SimpleDocker) ExtractRootfs(opts compiler.CompilerBackendOptions, keepP
defer os.RemoveAll(tempexport) // clean up
imageExport := filepath.Join(tempexport, "image.tar")
Spinner(22)
defer SpinnerStop()
if err := b.ExportImage(compiler.CompilerBackendOptions{ImageName: name, Destination: imageExport}); err != nil {
return errors.Wrap(err, "failed while extracting rootfs for "+name)
}
SpinnerStop()
src := imageExport
if src == "" && opts.ImageName != "" {

View File

@@ -35,26 +35,20 @@ func NewSimpleImgBackend() compiler.CompilerBackend {
// TODO: Missing still: labels, and build args expansion
func (*SimpleImg) BuildImage(opts compiler.CompilerBackendOptions) error {
name := opts.ImageName
path := opts.SourcePath
context := opts.Context
if context == "" {
context = "."
}
dockerfileName := opts.DockerFileName
buildarg := genBuildCommand(opts)
buildarg := []string{"build", "-f", dockerfileName, "-t", name, context}
Spinner(22)
defer SpinnerStop()
Info(":tea: Building image " + name)
cmd := exec.Command("img", buildarg...)
cmd.Dir = path
out, err := cmd.CombinedOutput()
cmd := exec.Command("img", buildarg...)
cmd.Dir = opts.SourcePath
err := runCommand(cmd)
if err != nil {
return errors.Wrap(err, "Failed building image: "+string(out))
return err
}
Info(":tea: Building image " + name + " done")
return nil
}
@@ -73,11 +67,13 @@ func (*SimpleImg) RemoveImage(opts compiler.CompilerBackendOptions) error {
}
func (*SimpleImg) DownloadImage(opts compiler.CompilerBackendOptions) error {
name := opts.ImageName
buildarg := []string{"pull", name}
Debug(":tea: Downloading image " + name)
Spinner(22)
defer SpinnerStop()
cmd := exec.Command("img", buildarg...)
out, err := cmd.CombinedOutput()
if err != nil {
@@ -138,6 +134,10 @@ func (*SimpleImg) ExportImage(opts compiler.CompilerBackendOptions) error {
path := opts.Destination
buildarg := []string{"save", "-o", path, name}
Debug(":tea: Saving image " + name)
Spinner(22)
defer SpinnerStop()
out, err := exec.Command("img", buildarg...).CombinedOutput()
if err != nil {
return errors.Wrap(err, "Failed exporting image: "+string(out))
@@ -158,8 +158,13 @@ func (s *SimpleImg) ExtractRootfs(opts compiler.CompilerBackendOptions, keepPerm
}
os.RemoveAll(path)
buildarg := []string{"unpack", "-o", path, name}
Debug(":tea: Extracting image " + name)
Spinner(22)
defer SpinnerStop()
out, err := exec.Command("img", buildarg...).CombinedOutput()
if err != nil {
return errors.Wrap(err, "Failed extracting image: "+string(out))

View File

@@ -0,0 +1,48 @@
// Copyright © 2021 Daniele Rondina <geaaru@sabayonlinux.org>
// Ettore Di Giacinto <mudler@sabayonlinux.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package backend
import (
"bytes"
. "github.com/mudler/luet/pkg/logger"
)
type BackendWriter struct {
BufferedOutput bool
Buffer *bytes.Buffer
}
func NewBackendWriter(buffered bool) *BackendWriter {
return &BackendWriter{
BufferedOutput: buffered,
Buffer: &bytes.Buffer{},
}
}
func (b *BackendWriter) Write(p []byte) (int, error) {
if b.BufferedOutput {
return b.Buffer.Write(p)
}
Msg("info", false, false, (string(p)))
return len(p), nil
}
func (b *BackendWriter) Close() error { return nil }
func (b *BackendWriter) GetCombinedOutput() string { return b.Buffer.String() }

View File

@@ -51,6 +51,7 @@ type LuetCompiler struct {
CompressionType CompressionImplementation
Options CompilerOptions
SolverOptions solver.Options
BackedArgs []string
}
func NewLuetCompiler(backend CompilerBackend, db pkg.PackageDatabase, opt *CompilerOptions, solvopts solver.Options) Compiler {
@@ -70,7 +71,9 @@ func NewLuetCompiler(backend CompilerBackend, db pkg.PackageDatabase, opt *Compi
SolverOptions: solvopts,
}
}
func (cs *LuetCompiler) SetBackendArgs(args []string) {
cs.BackedArgs = args
}
func (cs *LuetCompiler) SetConcurrency(i int) {
cs.Concurrency = i
}
@@ -148,8 +151,6 @@ func (cs *LuetCompiler) CompileWithReverseDeps(keepPermissions bool, ps Compilat
}
func (cs *LuetCompiler) CompileParallel(keepPermissions bool, ps CompilationSpecs) ([]Artifact, []error) {
Spinner(22)
defer SpinnerStop()
all := make(chan CompilationSpec)
artifacts := []Artifact{}
mutex := &sync.Mutex{}
@@ -383,12 +384,14 @@ func (cs *LuetCompiler) buildPackageImage(image, buildertaggedImage, packageImag
SourcePath: buildDir,
DockerFileName: p.GetPackage().GetFingerPrint() + "-builder.dockerfile",
Destination: p.Rel(p.GetPackage().GetFingerPrint() + "-builder.image.tar"),
BackendArgs: cs.BackedArgs,
}
runnerOpts = CompilerBackendOptions{
ImageName: packageImage,
SourcePath: buildDir,
DockerFileName: p.GetPackage().GetFingerPrint() + ".dockerfile",
Destination: p.Rel(p.GetPackage().GetFingerPrint() + ".image.tar"),
BackendArgs: cs.BackedArgs,
}
buildAndPush := func(opts CompilerBackendOptions) error {

View File

@@ -34,6 +34,8 @@ type Compiler interface {
FromDatabase(db pkg.PackageDatabase, minimum bool, dst string) ([]CompilationSpec, error)
SetBackend(CompilerBackend)
GetBackend() CompilerBackend
SetBackendArgs([]string)
SetCompressionType(t CompressionImplementation)
}
@@ -43,6 +45,7 @@ type CompilerBackendOptions struct {
DockerFileName string
Destination string
Context string
BackendArgs []string
}
type CompilerOptions struct {

View File

@@ -71,6 +71,10 @@ func (c *DockerClient) DownloadArtifact(artifact compiler.Artifact) (compiler.Ar
//var u *url.URL = nil
var err error
var temp string
Spinner(22)
defer SpinnerStop()
var resultingArtifact compiler.Artifact
artifactName := path.Base(artifact.GetPath())
cacheFile := filepath.Join(config.LuetCfg.GetSystem().GetSystemPkgsCacheDirPath(), artifactName)
@@ -102,9 +106,9 @@ func (c *DockerClient) DownloadArtifact(artifact compiler.Artifact) (compiler.Ar
defer os.RemoveAll(temp)
for _, uri := range c.RepoData.Urls {
Debug("Downloading artifact", artifactName, "from", uri)
imageName := fmt.Sprintf("%s:%s", uri, artifact.GetCompileSpec().GetPackage().GetFingerPrint())
Info("Downloading image", imageName)
// imageName := fmt.Sprintf("%s/%s", uri, artifact.GetCompileSpec().GetPackage().GetPackageImageName())
err = downloadAndExtractDockerImage(imageName, temp)

View File

@@ -8,6 +8,7 @@ import (
"runtime"
"strings"
"sync"
. "github.com/mudler/luet/pkg/config"
"github.com/briandowns/spinner"
@@ -20,7 +21,7 @@ import (
var s *spinner.Spinner = nil
var z *zap.Logger = nil
var aurora Aurora = nil
var spinnerLock = sync.Mutex{}
func NewSpinner() {
if s == nil {
s = spinner.New(
@@ -84,6 +85,8 @@ func ZapLogger() error {
}
func Spinner(i int) {
spinnerLock.Lock()
defer spinnerLock.Unlock()
var confLevel int
if LuetCfg.GetGeneral().Debug {
confLevel = 3
@@ -120,6 +123,8 @@ func SpinnerText(suffix, prefix string) {
}
func SpinnerStop() {
spinnerLock.Lock()
defer spinnerLock.Unlock()
var confLevel int
if LuetCfg.GetGeneral().Debug {
confLevel = 3
@@ -173,7 +178,7 @@ func level2AtomicLevel(level string) zap.AtomicLevel {
}
}
func msg(level string, withoutColor bool, msg ...interface{}) {
func Msg(level string, withoutColor, ln bool, msg ...interface{}) {
var message string
var confLevel, msgLevel int
@@ -219,11 +224,16 @@ func msg(level string, withoutColor bool, msg ...interface{}) {
log2File(level, message)
}
if ln {
fmt.Println(levelMsg)
} else {
fmt.Print(levelMsg)
}
}
func Warning(mess ...interface{}) {
msg("warning", false, mess...)
Msg("warning", false, true, mess...)
if LuetCfg.GetGeneral().FatalWarns {
os.Exit(2)
}
@@ -235,23 +245,23 @@ func Debug(mess ...interface{}) {
mess = append([]interface{}{fmt.Sprintf("DEBUG (%s:#%d:%v)",
path.Base(file), line, runtime.FuncForPC(pc).Name())}, mess...)
}
msg("debug", false, mess...)
Msg("debug", false, true, mess...)
}
func DebugC(mess ...interface{}) {
msg("debug", true, mess...)
Msg("debug", true, true, mess...)
}
func Info(mess ...interface{}) {
msg("info", false, mess...)
Msg("info", false, true, mess...)
}
func InfoC(mess ...interface{}) {
msg("info", true, mess...)
Msg("info", true, true, mess...)
}
func Error(mess ...interface{}) {
msg("error", false, mess...)
Msg("error", false, true, mess...)
}
func Fatal(mess ...interface{}) {

View File

@@ -32,6 +32,7 @@ var DBInMemoryInstance = &InMemoryDatabase{
CacheNoVersion: map[string]map[string]interface{}{},
ProvidesDatabase: map[string]map[string]Package{},
RevDepsDatabase: map[string]map[string]Package{},
cached: map[string]interface{}{},
}
type InMemoryDatabase struct {
@@ -199,6 +200,10 @@ func (db *InMemoryDatabase) populateCaches(p Package) {
// Create extra cache between package -> []versions
db.Lock()
if db.cached == nil {
db.cached = map[string]interface{}{}
}
if _, ok := db.cached[p.GetFingerPrint()]; ok {
db.Unlock()
return

View File

@@ -0,0 +1,74 @@
#!/bin/bash
export LUET_NOLOCK=true
oneTimeSetUp() {
export tmpdir="$(mktemp -d)"
}
oneTimeTearDown() {
rm -rf "$tmpdir"
}
testBuild() {
mkdir $tmpdir/testbuild
luet build --tree "$ROOT_DIR/tests/fixtures/buildableseed" --destination $tmpdir/testbuild --compression gzip test/c > /dev/null
buildst=$?
assertEquals 'builds successfully' "$buildst" "0"
assertTrue 'create package dep B' "[ -e '$tmpdir/testbuild/b-test-1.0.package.tar.gz' ]"
assertTrue 'create package' "[ -e '$tmpdir/testbuild/c-test-1.0.package.tar.gz' ]"
}
testRepo() {
assertTrue 'no repository' "[ ! -e '$tmpdir/testbuild/repository.yaml' ]"
luet create-repo --tree "$ROOT_DIR/tests/fixtures/buildableseed" \
--output $tmpdir/testbuild \
--packages $tmpdir/testbuild \
--name "test" \
--descr "Test Repo" \
--urls $tmpdir/testrootfs \
--type disk > /dev/null
createst=$?
assertEquals 'create repo successfully' "$createst" "0"
assertTrue 'create repository' "[ -e '$tmpdir/testbuild/repository.yaml' ]"
}
testConfig() {
mkdir $tmpdir/testrootfs
cat <<EOF > $tmpdir/luet.yaml
general:
debug: true
system:
rootfs: $tmpdir/testrootfs
database_engine: "memory"
config_from_host: true
repositories:
- name: "main"
type: "disk"
enable: true
urls:
- "$tmpdir/testbuild"
EOF
luet config --config $tmpdir/luet.yaml
res=$?
assertEquals 'config test successfully' "$res" "0"
}
testInstall() {
luet install -y --config $tmpdir/luet.yaml test/c
installst=$?
assertEquals 'install test successfully' "$installst" "0"
assertTrue 'package installed' "[ -e '$tmpdir/testrootfs/c' ]"
}
testCleanup() {
luet cleanup --config $tmpdir/luet.yaml
installst=$?
assertEquals 'install test successfully' "$installst" "0"
assertTrue 'package installed' "[ ! -e '$tmpdir/testrootfs/packages/c-test-1.0.package.tar.gz' ]"
}
# Load shUnit2.
. "$ROOT_DIR/tests/integration/shunit2"/shunit2

View File

@@ -1,7 +1,10 @@
arch:
- amd64
- ppc64le
language: go
go:
- 1.11
- 1.12.5
- 1.13
- 1.14.1
env:
- GOARCH: amd64
- GOARCH: 386

View File

@@ -17,52 +17,52 @@ go get github.com/briandowns/spinner
## Available Character Sets
(Numbered by their slice index)
index | character set | sample gif
------|---------------|---------------
0 | ```←↖↑↗→↘↓↙``` | ![Sample Gif](gifs/0.gif)
1 | ```▁▃▄▅▆▇█▇▆▅▄▃▁``` | ![Sample Gif](gifs/1.gif)
2 | ```▖▘▝▗``` | ![Sample Gif](gifs/2.gif)
3 | ```┤┘┴└├┌┬┐``` | ![Sample Gif](gifs/3.gif)
4 | ```◢◣◤◥``` | ![Sample Gif](gifs/4.gif)
5 | ```◰◳◲◱``` | ![Sample Gif](gifs/5.gif)
6 | ```◴◷◶◵``` | ![Sample Gif](gifs/6.gif)
7 | ```◐◓◑◒``` | ![Sample Gif](gifs/7.gif)
8 | ```.oO@*``` | ![Sample Gif](gifs/8.gif)
9 | ```|/-\``` | ![Sample Gif](gifs/9.gif)
10 | ```◡◡⊙⊙◠◠``` | ![Sample Gif](gifs/10.gif)
11 | ```⣾⣽⣻⢿⡿⣟⣯⣷``` | ![Sample Gif](gifs/11.gif)
12 | ```>))'> >))'> >))'> >))'> >))'> <'((< <'((< <'((<``` | ![Sample Gif](gifs/12.gif)
13 | ```⠁⠂⠄⡀⢀⠠⠐⠈``` | ![Sample Gif](gifs/13.gif)
14 | ```⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏``` | ![Sample Gif](gifs/14.gif)
15 | ```abcdefghijklmnopqrstuvwxyz``` | ![Sample Gif](gifs/15.gif)
16 | ```▉▊▋▌▍▎▏▎▍▌▋▊▉``` | ![Sample Gif](gifs/16.gif)
17 | ```■□▪▫``` | ![Sample Gif](gifs/17.gif)
18 | ```←↑→↓``` | ![Sample Gif](gifs/18.gif)
19 | ```╫╪``` | ![Sample Gif](gifs/19.gif)
20 | ```⇐⇖⇑⇗⇒⇘⇓⇙``` | ![Sample Gif](gifs/20.gif)
21 | ```⠁⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈⠈``` | ![Sample Gif](gifs/21.gif)
22 | ```⠈⠉⠋⠓⠒⠐⠐⠒⠖⠦⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈``` | ![Sample Gif](gifs/22.gif)
23 | ```⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠴⠲⠒⠂⠂⠒⠚⠙⠉⠁``` | ![Sample Gif](gifs/23.gif)
24 | ```⠋⠙⠚⠒⠂⠂⠒⠲⠴⠦⠖⠒⠐⠐⠒⠓⠋``` | ![Sample Gif](gifs/24.gif)
25 | ```ヲァィゥェォャュョッアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン``` | ![Sample Gif](gifs/25.gif)
26 | ```. .. ...``` | ![Sample Gif](gifs/26.gif)
27 | ```▁▂▃▄▅▆▇█▉▊▋▌▍▎▏▏▎▍▌▋▊▉█▇▆▅▄▃▂▁``` | ![Sample Gif](gifs/27.gif)
28 | ```.oO°Oo.``` | ![Sample Gif](gifs/28.gif)
29 | ```+x``` | ![Sample Gif](gifs/29.gif)
30 | ```v<^>``` | ![Sample Gif](gifs/30.gif)
31 | ```>>---> >>---> >>---> >>---> >>---> <---<< <---<< <---<< <---<< <---<<``` | ![Sample Gif](gifs/31.gif)
32 | ```| || ||| |||| ||||| |||||| ||||| |||| ||| || |``` | ![Sample Gif](gifs/32.gif)
33 | ```[] [=] [==] [===] [====] [=====] [======] [=======] [========] [=========] [==========]``` | ![Sample Gif](gifs/33.gif)
34 | ```(*---------) (-*--------) (--*-------) (---*------) (----*-----) (-----*----) (------*---) (-------*--) (--------*-) (---------*)``` | ![Sample Gif](gifs/34.gif)
35 | ```█▒▒▒▒▒▒▒▒▒ ███▒▒▒▒▒▒▒ █████▒▒▒▒▒ ███████▒▒▒ ██████████``` | ![Sample Gif](gifs/35.gif)
36 | ```[ ] [=> ] [===> ] [=====> ] [======> ] [========> ] [==========> ] [============> ] [==============> ] [================> ] [==================> ] [===================>]``` | ![Sample Gif](gifs/36.gif)
37 | ```🕐 🕑 🕒 🕓 🕔 🕕 🕖 🕗 🕘 🕙 🕚 🕛``` | ![Sample Gif](gifs/37.gif)
38 | ```🕐 🕜 🕑 🕝 🕒 🕞 🕓 🕟 🕔 🕠 🕕 🕡 🕖 🕢 🕗 🕣 🕘 🕤 🕙 🕥 🕚 🕦 🕛 🕧``` | ![Sample Gif](gifs/38.gif)
39 | ```🌍 🌎 🌏``` | ![Sample Gif](gifs/39.gif)
40 | ```◜ ◝ ◞ ◟``` | ![Sample Gif](gifs/40.gif)
41 | ```⬒ ⬔ ⬓ ⬕``` | ![Sample Gif](gifs/41.gif)
42 | ```⬖ ⬘ ⬗ ⬙``` | ![Sample Gif](gifs/42.gif)
43 | ```[>>> >] []>>>> [] [] >>>> [] [] >>>> [] [] >>>> [] [] >>>>[] [>> >>]``` | ![Sample Gif](gifs/43.gif)
| index | character set | sample gif |
| ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- |
| 0 | ```←↖↑↗→↘↓↙``` | ![Sample Gif](gifs/0.gif) |
| 1 | ```▁▃▄▅▆▇█▇▆▅▄▃▁``` | ![Sample Gif](gifs/1.gif) |
| 2 | ```▖▘▝▗``` | ![Sample Gif](gifs/2.gif) |
| 3 | ```┤┘┴└├┌┬┐``` | ![Sample Gif](gifs/3.gif) |
| 4 | ```◢◣◤◥``` | ![Sample Gif](gifs/4.gif) |
| 5 | ```◰◳◲◱``` | ![Sample Gif](gifs/5.gif) |
| 6 | ```◴◷◶◵``` | ![Sample Gif](gifs/6.gif) |
| 7 | ```◐◓◑◒``` | ![Sample Gif](gifs/7.gif) |
| 8 | ```.oO@*``` | ![Sample Gif](gifs/8.gif) |
| 9 | ```\|/-\``` | ![Sample Gif](gifs/9.gif) |
| 10 | ```◡◡⊙⊙◠◠``` | ![Sample Gif](gifs/10.gif) |
| 11 | ```⣾⣽⣻⢿⡿⣟⣯⣷``` | ![Sample Gif](gifs/11.gif) |
| 12 | ```>))'> >))'> >))'> >))'> >))'> <'((< <'((< <'((<``` | ![Sample Gif](gifs/12.gif) |
| 13 | ```⠁⠂⠄⡀⢀⠠⠐⠈``` | ![Sample Gif](gifs/13.gif) |
| 14 | ```⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏``` | ![Sample Gif](gifs/14.gif) |
| 15 | ```abcdefghijklmnopqrstuvwxyz``` | ![Sample Gif](gifs/15.gif) |
| 16 | ```▉▊▋▌▍▎▏▎▍▌▋▊▉``` | ![Sample Gif](gifs/16.gif) |
| 17 | ```■□▪▫``` | ![Sample Gif](gifs/17.gif) |
| 18 | ```←↑→↓``` | ![Sample Gif](gifs/18.gif) |
| 19 | ```╫╪``` | ![Sample Gif](gifs/19.gif) |
| 20 | ```⇐⇖⇑⇗⇒⇘⇓⇙``` | ![Sample Gif](gifs/20.gif) |
| 21 | ```⠁⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈⠈``` | ![Sample Gif](gifs/21.gif) |
| 22 | ```⠈⠉⠋⠓⠒⠐⠐⠒⠖⠦⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈``` | ![Sample Gif](gifs/22.gif) |
| 23 | ```⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠴⠲⠒⠂⠂⠒⠚⠙⠉⠁``` | ![Sample Gif](gifs/23.gif) |
| 24 | ```⠋⠙⠚⠒⠂⠂⠒⠲⠴⠦⠖⠒⠐⠐⠒⠓⠋``` | ![Sample Gif](gifs/24.gif) |
| 25 | ```ヲァィゥェォャュョッアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン``` | ![Sample Gif](gifs/25.gif) |
| 26 | ```. .. ...``` | ![Sample Gif](gifs/26.gif) |
| 27 | ```▁▂▃▄▅▆▇█▉▊▋▌▍▎▏▏▎▍▌▋▊▉█▇▆▅▄▃▂▁``` | ![Sample Gif](gifs/27.gif) |
| 28 | ```.oO°Oo.``` | ![Sample Gif](gifs/28.gif) |
| 29 | ```+x``` | ![Sample Gif](gifs/29.gif) |
| 30 | ```v<^>``` | ![Sample Gif](gifs/30.gif) |
| 31 | ```>>---> >>---> >>---> >>---> >>---> <---<< <---<< <---<< <---<< <---<<``` | ![Sample Gif](gifs/31.gif) |
| 32 | ```\| \|\| \|\|\| \|\|\|\| \|\|\|\|\| \|\|\|\|\|\| \|\|\|\|\| \|\|\|\| \|\|\| \|\| \|``` | ![Sample Gif](gifs/32.gif) |
| 33 | ```[] [=] [==] [===] [====] [=====] [======] [=======] [========] [=========] [==========]``` | ![Sample Gif](gifs/33.gif) |
| 34 | ```(*---------) (-*--------) (--*-------) (---*------) (----*-----) (-----*----) (------*---) (-------*--) (--------*-) (---------*)``` | ![Sample Gif](gifs/34.gif) |
| 35 | ```█▒▒▒▒▒▒▒▒▒ ███▒▒▒▒▒▒▒ █████▒▒▒▒▒ ███████▒▒▒ ██████████``` | ![Sample Gif](gifs/35.gif) |
| 36 | ```[ ] [=> ] [===> ] [=====> ] [======> ] [========> ] [==========> ] [============> ] [==============> ] [================> ] [==================> ] [===================>]``` | ![Sample Gif](gifs/36.gif) |
| 37 | ```🕐 🕑 🕒 🕓 🕔 🕕 🕖 🕗 🕘 🕙 🕚 🕛``` | ![Sample Gif](gifs/37.gif) |
| 38 | ```🕐 🕜 🕑 🕝 🕒 🕞 🕓 🕟 🕔 🕠 🕕 🕡 🕖 🕢 🕗 🕣 🕘 🕤 🕙 🕥 🕚 🕦 🕛 🕧``` | ![Sample Gif](gifs/38.gif) |
| 39 | ```🌍 🌎 🌏``` | ![Sample Gif](gifs/39.gif) |
| 40 | ```◜ ◝ ◞ ◟``` | ![Sample Gif](gifs/40.gif) |
| 41 | ```⬒ ⬔ ⬓ ⬕``` | ![Sample Gif](gifs/41.gif) |
| 42 | ```⬖ ⬘ ⬗ ⬙``` | ![Sample Gif](gifs/42.gif) |
| 43 | ```[>>> >] []>>>> [] [] >>>> [] [] >>>> [] [] >>>> [] [] >>>>[] [>> >>]``` | ![Sample Gif](gifs/43.gif) |
## Features
@@ -139,7 +139,7 @@ s.Prefix = "prefixed text: " // Prefix text before the spinner
s.Suffix = " :appended text" // Append text after the spinner
```
## Set or change the color of the spinner. Default color is white. This will restart the spinner with the new color.
## Set or change the color of the spinner. Default color is white. The spinner will need to be restarted to pick up the change.
```Go
s.Color("red") // Set the spinner color to red
@@ -242,10 +242,11 @@ Feature suggested and write up by [dekz](https://github.com/dekz)
Setting the Spinner Writer to Stderr helps show progress to the user, with the enhancement to chain, pipe or redirect the output.
This is the preferred method of setting a Writer at this time.
```go
s := spinner.New(spinner.CharSets[11], 100*time.Millisecond)
s := spinner.New(spinner.CharSets[11], 100*time.Millisecond, spinner.WithWriter(os.Stderr))
s.Suffix = " Encrypting data..."
s.Writer = os.Stderr
s.Start()
// Encrypt the data into ciphertext
fmt.Println(os.Stdout, ciphertext)

View File

@@ -1,5 +1,7 @@
module github.com/briandowns/spinner
go 1.14
require (
github.com/fatih/color v1.7.0
github.com/mattn/go-colorable v0.1.2 // indirect

View File

@@ -14,13 +14,13 @@
package spinner
import (
"encoding/hex"
"errors"
"fmt"
"io"
"os"
"runtime"
"strconv"
"strings"
"sync"
"time"
"unicode/utf8"
@@ -161,7 +161,7 @@ var colorAttributeMap = map[string]color.Attribute{
"bgHiWhite": color.BgHiWhite,
}
// validColor will make sure the given color is actually allowed
// validColor will make sure the given color is actually allowed.
func validColor(c string) bool {
if validColors[c] {
return true
@@ -169,8 +169,9 @@ func validColor(c string) bool {
return false
}
// Spinner struct to hold the provided options
// Spinner struct to hold the provided options.
type Spinner struct {
mu *sync.RWMutex //
Delay time.Duration // Delay is the speed of the indicator
chars []string // chars holds the chosen character set
Prefix string // Prefix is the text preppended to the indicator
@@ -178,20 +179,21 @@ type Spinner struct {
FinalMSG string // string displayed after Stop() is called
lastOutput string // last character(set) written
color func(a ...interface{}) string // default color is white
lock *sync.RWMutex //
Writer io.Writer // to make testing better, exported so users have access
Writer io.Writer // to make testing better, exported so users have access. Use `WithWriter` to update after initialization.
active bool // active holds the state of the spinner
stopChan chan struct{} // stopChan is a channel used to stop the indicator
HideCursor bool // hideCursor determines if the cursor is visible
PreUpdate func(s *Spinner) // will be triggered before every spinner update
PostUpdate func(s *Spinner) // will be triggered after every spinner update
}
// New provides a pointer to an instance of Spinner with the supplied options
// New provides a pointer to an instance of Spinner with the supplied options.
func New(cs []string, d time.Duration, options ...Option) *Spinner {
s := &Spinner{
Delay: d,
chars: cs,
color: color.New(color.FgWhite).SprintFunc(),
lock: &sync.RWMutex{},
mu: &sync.RWMutex{},
Writer: color.Output,
active: false,
stopChan: make(chan struct{}, 1),
@@ -204,10 +206,10 @@ func New(cs []string, d time.Duration, options ...Option) *Spinner {
}
// Option is a function that takes a spinner and applies
// a given configuration
// a given configuration.
type Option func(*Spinner)
// Options contains fields to configure the spinner
// Options contains fields to configure the spinner.
type Options struct {
Color string
Suffix string
@@ -215,7 +217,7 @@ type Options struct {
HideCursor bool
}
// WithColor adds the given color to the spinner
// WithColor adds the given color to the spinner.
func WithColor(color string) Option {
return func(s *Spinner) {
s.Color(color)
@@ -223,7 +225,7 @@ func WithColor(color string) Option {
}
// WithSuffix adds the given string to the spinner
// as the suffix
// as the suffix.
func WithSuffix(suffix string) Option {
return func(s *Spinner) {
s.Suffix = suffix
@@ -231,7 +233,7 @@ func WithSuffix(suffix string) Option {
}
// WithFinalMSG adds the given string ot the spinner
// as the final message to be written
// as the final message to be written.
func WithFinalMSG(finalMsg string) Option {
return func(s *Spinner) {
s.FinalMSG = finalMsg
@@ -239,31 +241,42 @@ func WithFinalMSG(finalMsg string) Option {
}
// WithHiddenCursor hides the cursor
// if hideCursor = true given
// if hideCursor = true given.
func WithHiddenCursor(hideCursor bool) Option {
return func(s *Spinner) {
s.HideCursor = hideCursor
}
}
// Active will return whether or not the spinner is currently active
// WithWriter adds the given writer to the spinner. This
// function should be favored over directly assigning to
// the struct value.
func WithWriter(w io.Writer) Option {
return func(s *Spinner) {
s.mu.Lock()
s.Writer = w
s.mu.Unlock()
}
}
// Active will return whether or not the spinner is currently active.
func (s *Spinner) Active() bool {
return s.active
}
// Start will start the indicator
// Start will start the indicator.
func (s *Spinner) Start() {
s.lock.Lock()
s.mu.Lock()
if s.active {
s.lock.Unlock()
s.mu.Unlock()
return
}
if s.HideCursor && runtime.GOOS != "windows" {
// hides the cursor
fmt.Print("\033[?25l")
fmt.Fprint(s.Writer, "\033[?25l")
}
s.active = true
s.lock.Unlock()
s.mu.Unlock()
go func() {
for {
@@ -272,8 +285,17 @@ func (s *Spinner) Start() {
case <-s.stopChan:
return
default:
s.lock.Lock()
s.mu.Lock()
if !s.active {
s.mu.Unlock()
return
}
s.erase()
if s.PreUpdate != nil {
s.PreUpdate(s)
}
var outColor string
if runtime.GOOS == "windows" {
if s.Writer == os.Stderr {
@@ -282,14 +304,18 @@ func (s *Spinner) Start() {
outColor = fmt.Sprintf("\r%s%s%s ", s.Prefix, s.color(s.chars[i]), s.Suffix)
}
} else {
outColor = fmt.Sprintf("%s%s%s ", s.Prefix, s.color(s.chars[i]), s.Suffix)
outColor = fmt.Sprintf("\r%s%s%s ", s.Prefix, s.color(s.chars[i]), s.Suffix)
}
outPlain := fmt.Sprintf("%s%s%s ", s.Prefix, s.chars[i], s.Suffix)
outPlain := fmt.Sprintf("\r%s%s%s ", s.Prefix, s.chars[i], s.Suffix)
fmt.Fprint(s.Writer, outColor)
s.lastOutput = outPlain
delay := s.Delay
s.lock.Unlock()
if s.PostUpdate != nil {
s.PostUpdate(s)
}
s.mu.Unlock()
time.Sleep(delay)
}
}
@@ -297,40 +323,41 @@ func (s *Spinner) Start() {
}()
}
// Stop stops the indicator
// Stop stops the indicator.
func (s *Spinner) Stop() {
s.lock.Lock()
defer s.lock.Unlock()
s.mu.Lock()
defer s.mu.Unlock()
if s.active {
s.active = false
if s.HideCursor && runtime.GOOS != "windows" {
// makes the cursor visible
fmt.Print("\033[?25h")
fmt.Fprint(s.Writer, "\033[?25h")
}
s.erase()
if s.FinalMSG != "" {
fmt.Fprintf(s.Writer, s.FinalMSG)
fmt.Fprint(s.Writer, s.FinalMSG)
}
s.stopChan <- struct{}{}
}
}
// Restart will stop and start the indicator
// Restart will stop and start the indicator.
func (s *Spinner) Restart() {
s.Stop()
s.Start()
}
// Reverse will reverse the order of the slice assigned to the indicator
// Reverse will reverse the order of the slice assigned to the indicator.
func (s *Spinner) Reverse() {
s.lock.Lock()
defer s.lock.Unlock()
s.mu.Lock()
defer s.mu.Unlock()
for i, j := 0, len(s.chars)-1; i < j; i, j = i+1, j-1 {
s.chars[i], s.chars[j] = s.chars[j], s.chars[i]
}
}
// Color will set the struct field for the given color to be used
// Color will set the struct field for the given color to be used. The spinner
// will need to be explicitly restarted.
func (s *Spinner) Color(colors ...string) error {
colorAttributes := make([]color.Attribute, len(colors))
@@ -342,63 +369,55 @@ func (s *Spinner) Color(colors ...string) error {
colorAttributes[index] = colorAttributeMap[c]
}
s.lock.Lock()
s.mu.Lock()
s.color = color.New(colorAttributes...).SprintFunc()
s.lock.Unlock()
s.Restart()
s.mu.Unlock()
return nil
}
// UpdateSpeed will set the indicator delay to the given value
// UpdateSpeed will set the indicator delay to the given value.
func (s *Spinner) UpdateSpeed(d time.Duration) {
s.lock.Lock()
defer s.lock.Unlock()
s.mu.Lock()
defer s.mu.Unlock()
s.Delay = d
}
// UpdateCharSet will change the current character set to the given one
// UpdateCharSet will change the current character set to the given one.
func (s *Spinner) UpdateCharSet(cs []string) {
s.lock.Lock()
defer s.lock.Unlock()
s.mu.Lock()
defer s.mu.Unlock()
s.chars = cs
}
// erase deletes written characters
//
// erase deletes written characters.
// Caller must already hold s.lock.
func (s *Spinner) erase() {
n := utf8.RuneCountInString(s.lastOutput)
if runtime.GOOS == "windows" {
var clearString string
for i := 0; i < n; i++ {
clearString += " "
}
clearString += "\r"
fmt.Fprintf(s.Writer, clearString)
clearString := "\r" + strings.Repeat(" ", n) + "\r"
fmt.Fprint(s.Writer, clearString)
s.lastOutput = ""
return
}
del, _ := hex.DecodeString("7f")
for _, c := range []string{"\b", string(del), "\b", "\033[K"} { // "\033[K" for macOS Terminal
for i := 0; i < n; i++ {
fmt.Fprintf(s.Writer, c)
}
for _, c := range []string{"\b", "\127", "\b", "\033[K"} { // "\033[K" for macOS Terminal
fmt.Fprint(s.Writer, strings.Repeat(c, n))
}
fmt.Fprintf(s.Writer, "\r\033[K") // erases to end of line
s.lastOutput = ""
}
// Lock allows for manual control to lock the spinner
// Lock allows for manual control to lock the spinner.
func (s *Spinner) Lock() {
s.lock.Lock()
s.mu.Lock()
}
// Unlock allows for manual control to unlock the spinner
// Unlock allows for manual control to unlock the spinner.
func (s *Spinner) Unlock() {
s.lock.Unlock()
s.mu.Unlock()
}
// GenerateNumberSequence will generate a slice of integers at the
// provided length and convert them each to a string
// provided length and convert them each to a string.
func GenerateNumberSequence(length int) []string {
numSeq := make([]string, length)
for i := 0; i < length; i++ {

2
vendor/modules.txt vendored
View File

@@ -49,7 +49,7 @@ github.com/asdine/storm/codec/json
github.com/asdine/storm/index
github.com/asdine/storm/internal
github.com/asdine/storm/q
# github.com/briandowns/spinner v1.7.0
# github.com/briandowns/spinner v1.12.1-0.20201220203425-e201aaea0a31
## explicit
github.com/briandowns/spinner
# github.com/cavaliercoder/grab v1.0.1-0.20201108051000-98a5bfe305ec