mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2025-12-30 17:00:57 -06:00
Add a "posixfs consistency" command
This command checks posixfs storages for inconsistencies and fixes them.
This commit is contained in:
1
go.mod
1
go.mod
@@ -79,6 +79,7 @@ require (
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/test-go/testify v1.1.4
|
||||
github.com/theckman/yacspin v0.13.12
|
||||
github.com/thejerf/suture/v4 v4.0.6
|
||||
github.com/tidwall/gjson v1.18.0
|
||||
github.com/tus/tusd/v2 v2.8.0
|
||||
|
||||
2
go.sum
2
go.sum
@@ -1083,6 +1083,8 @@ github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE
|
||||
github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU=
|
||||
github.com/thanhpk/randstr v1.0.6 h1:psAOktJFD4vV9NEVb3qkhRSMvYh4ORRaj1+w/hn4B+o=
|
||||
github.com/thanhpk/randstr v1.0.6/go.mod h1:M/H2P1eNLZzlDwAzpkkkUvoyNNMbzRGhESZuEQk3r0U=
|
||||
github.com/theckman/yacspin v0.13.12 h1:CdZ57+n0U6JMuh2xqjnjRq5Haj6v1ner2djtLQRzJr4=
|
||||
github.com/theckman/yacspin v0.13.12/go.mod h1:Rd2+oG2LmQi5f3zC3yeZAOl245z8QOvrH4OPOJNZxLg=
|
||||
github.com/thejerf/suture/v4 v4.0.6 h1:QsuCEsCqb03xF9tPAsWAj8QOAJBgQI1c0VqJNaingg8=
|
||||
github.com/thejerf/suture/v4 v4.0.6/go.mod h1:gu9Y4dXNUWFrByqRt30Rm9/UZ0wzRSt9AJS6xu/ZGxU=
|
||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||
|
||||
376
opencloud/pkg/command/posixfs.go
Normal file
376
opencloud/pkg/command/posixfs.go
Normal file
@@ -0,0 +1,376 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/opencloud/pkg/register"
|
||||
"github.com/opencloud-eu/opencloud/pkg/config"
|
||||
"github.com/pkg/xattr"
|
||||
"github.com/theckman/yacspin"
|
||||
"github.com/urfave/cli/v2"
|
||||
"github.com/vmihailenco/msgpack/v5"
|
||||
)
|
||||
|
||||
// Define the names of the extended attributes we are working with.
|
||||
const (
|
||||
parentIDAttrName = "user.oc.parentid"
|
||||
idAttrName = "user.oc.id"
|
||||
spaceIDAttrName = "user.oc.space.id"
|
||||
ownerIDAttrName = "user.oc.owner.id"
|
||||
)
|
||||
|
||||
var (
|
||||
spinner *yacspin.Spinner
|
||||
restartRequired = false
|
||||
)
|
||||
|
||||
// EntryInfo holds information about a directory entry.
|
||||
type EntryInfo struct {
|
||||
Path string
|
||||
ModTime time.Time
|
||||
ParentID string
|
||||
}
|
||||
|
||||
// PosixfsCommand is the entrypoint for the groups command.
|
||||
func PosixfsCommand(cfg *config.Config) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "posixfs",
|
||||
Usage: `cli tools to inspect and manipulate a posixfs storage.`,
|
||||
Category: "maintenance",
|
||||
Subcommands: []*cli.Command{
|
||||
consistencyCmd(cfg),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
register.AddCommand(PosixfsCommand)
|
||||
}
|
||||
|
||||
// consistencyCmd returns a command to check the consistency of the posixfs storage.
|
||||
func consistencyCmd(cfg *config.Config) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "consistency",
|
||||
Usage: "check the consistency of the posixfs storage",
|
||||
Action: func(c *cli.Context) error {
|
||||
return checkPosixfsConsistency(c, cfg)
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "root",
|
||||
Aliases: []string{"r"},
|
||||
Required: true,
|
||||
Usage: "Path to the root directory of the posixfs storage",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// checkPosixfsConsistency checks the consistency of the posixfs storage.
|
||||
func checkPosixfsConsistency(c *cli.Context, cfg *config.Config) error {
|
||||
rootPath := c.String("root")
|
||||
indexesPath := filepath.Join(rootPath, "indexes")
|
||||
|
||||
_, err := os.Stat(indexesPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return fmt.Errorf("consistency check failed: '%s' is not a posixfs root", rootPath)
|
||||
}
|
||||
return fmt.Errorf("error accessing '%s': %w", indexesPath, err)
|
||||
}
|
||||
|
||||
spinnerCfg := yacspin.Config{
|
||||
Frequency: 100 * time.Millisecond,
|
||||
CharSet: yacspin.CharSets[11],
|
||||
StopCharacter: "✓",
|
||||
StopColors: []string{"fgGreen"},
|
||||
StopFailCharacter: "✗",
|
||||
StopFailColors: []string{"fgRed"},
|
||||
}
|
||||
|
||||
spinner, err = yacspin.New(spinnerCfg)
|
||||
err = spinner.Start()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating spinner: %w", err)
|
||||
}
|
||||
|
||||
checkSpaces(filepath.Join(rootPath, "users"))
|
||||
spinner.Suffix(" Personal spaces check ")
|
||||
spinner.StopMessage("completed")
|
||||
spinner.Stop()
|
||||
|
||||
checkSpaces(filepath.Join(rootPath, "projects"))
|
||||
spinner.Suffix(" Project spaces check ")
|
||||
spinner.StopMessage("completed")
|
||||
spinner.Stop()
|
||||
|
||||
if restartRequired {
|
||||
fmt.Println("\n\n ⚠️ Please restart your openCloud instance to apply changes.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkSpaces(basePath string) {
|
||||
dirEntries, err := os.ReadDir(basePath)
|
||||
if err != nil {
|
||||
spinner.Message(fmt.Sprintf("Error reading spaces directory '%s'\n", basePath))
|
||||
spinner.StopFail()
|
||||
return
|
||||
}
|
||||
|
||||
for _, entry := range dirEntries {
|
||||
if entry.IsDir() {
|
||||
fullPath := filepath.Join(basePath, entry.Name())
|
||||
checkSpace(fullPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkSpace(spacePath string) {
|
||||
spinner.Suffix(fmt.Sprintf(" Checking space '%s'", spacePath))
|
||||
|
||||
info, err := os.Stat(spacePath)
|
||||
if err != nil {
|
||||
logFailure("Error accessing path '%s': %v", spacePath, err)
|
||||
return
|
||||
}
|
||||
if !info.IsDir() {
|
||||
logFailure("Error: The provided path '%s' is not a directory\n", spacePath)
|
||||
return
|
||||
}
|
||||
|
||||
spaceID, err := xattr.Get(spacePath, spaceIDAttrName)
|
||||
if err != nil || len(spaceID) == 0 {
|
||||
logFailure("Error: The directory '%s' does not seem to be a space root, it's missing the '%s' attribute\n", spacePath, spaceIDAttrName)
|
||||
return
|
||||
}
|
||||
|
||||
checkSpaceID(spacePath)
|
||||
}
|
||||
|
||||
func checkSpaceID(spacePath string) {
|
||||
spinner.Message("checking space ID uniqueness")
|
||||
|
||||
entries, uniqueIDs, oldestEntry, err := gatherAttributes(spacePath)
|
||||
if err != nil {
|
||||
logFailure("Failed to gather attributes: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(entries) == 0 {
|
||||
logSuccess("(empty space)")
|
||||
return
|
||||
}
|
||||
|
||||
if len(uniqueIDs) > 1 {
|
||||
spinner.Pause()
|
||||
fmt.Println("\n ⚠ Multiple space IDs found:")
|
||||
for id := range uniqueIDs {
|
||||
fmt.Printf(" - %s\n", id)
|
||||
}
|
||||
|
||||
fmt.Printf("\n ⏳ Oldest entry is '%s' (modified on %s).\n",
|
||||
filepath.Base(oldestEntry.Path), oldestEntry.ModTime.Format(time.RFC1123))
|
||||
|
||||
targetID := oldestEntry.ParentID
|
||||
fmt.Printf(" ✅ Proposed target Parent ID: %s\n", targetID)
|
||||
|
||||
fmt.Printf("\n Do you want to unify all parent IDs to '%s'? This will modify %d entries, the directory, and the user index. (y/N): ", targetID, len(entries))
|
||||
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
input, _ := reader.ReadString('\n')
|
||||
input = strings.TrimSpace(strings.ToLower(input))
|
||||
|
||||
if input != "y" {
|
||||
spinner.Unpause()
|
||||
logFailure("Operation cancelled by user.")
|
||||
return
|
||||
}
|
||||
restartRequired = true
|
||||
fixSpaceID(spacePath, targetID, entries)
|
||||
spinner.Unpause()
|
||||
} else {
|
||||
logSuccess("")
|
||||
}
|
||||
}
|
||||
|
||||
func fixSpaceID(spacePath, targetID string, entries []EntryInfo) {
|
||||
// Set all parentid attributes to the proper space ID
|
||||
err := setAllParentIDAttributes(entries, targetID)
|
||||
if err != nil {
|
||||
logFailure("an error occurred during file attribute update: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Update space ID itself
|
||||
fmt.Printf(" Updating directory '%s' with attribute '%s' -> %s\n", filepath.Base(spacePath), idAttrName, targetID)
|
||||
err = xattr.Set(spacePath, idAttrName, []byte(targetID))
|
||||
if err != nil {
|
||||
logFailure("Failed to set attribute on directory '%s': %v", spacePath, err)
|
||||
return
|
||||
}
|
||||
err = xattr.Set(spacePath, spaceIDAttrName, []byte(targetID))
|
||||
if err != nil {
|
||||
logFailure("Failed to set attribute on directory '%s': %v", spacePath, err)
|
||||
return
|
||||
}
|
||||
|
||||
// update the index
|
||||
err = updateOwnerIndexFile(spacePath, targetID)
|
||||
if err != nil {
|
||||
logFailure("Could not update the owner index file: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func gatherAttributes(path string) ([]EntryInfo, map[string]struct{}, EntryInfo, error) {
|
||||
dirEntries, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
return nil, nil, EntryInfo{}, fmt.Errorf("failed to read directory: %w", err)
|
||||
}
|
||||
|
||||
var allEntries []EntryInfo
|
||||
uniqueIDs := make(map[string]struct{})
|
||||
var oldestEntry EntryInfo
|
||||
oldestTime := time.Now().Add(100 * 365 * 24 * time.Hour) // Set to a future date to find the oldest entry
|
||||
|
||||
for _, entry := range dirEntries {
|
||||
fullPath := filepath.Join(path, entry.Name())
|
||||
info, err := os.Stat(fullPath)
|
||||
if err != nil {
|
||||
fmt.Printf(" - Warning: could not stat %s: %v\n", entry.Name(), err)
|
||||
continue
|
||||
}
|
||||
|
||||
parentID, err := xattr.Get(fullPath, parentIDAttrName)
|
||||
if err != nil {
|
||||
continue // Skip if attribute doesn't exist or can't be read
|
||||
}
|
||||
|
||||
entryInfo := EntryInfo{
|
||||
Path: fullPath,
|
||||
ModTime: info.ModTime(),
|
||||
ParentID: string(parentID),
|
||||
}
|
||||
|
||||
allEntries = append(allEntries, entryInfo)
|
||||
uniqueIDs[string(parentID)] = struct{}{}
|
||||
|
||||
if entryInfo.ModTime.Before(oldestTime) {
|
||||
oldestTime = entryInfo.ModTime
|
||||
oldestEntry = entryInfo
|
||||
}
|
||||
}
|
||||
|
||||
return allEntries, uniqueIDs, oldestEntry, nil
|
||||
}
|
||||
|
||||
func setAllParentIDAttributes(entries []EntryInfo, targetID string) error {
|
||||
fmt.Printf(" Setting all parent IDs to '%s':\n", targetID)
|
||||
|
||||
for _, entry := range entries {
|
||||
if entry.ParentID == targetID {
|
||||
fmt.Printf(" - Skipping '%s' (already has target ID).\n", filepath.Base(entry.Path))
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Printf(" - Removing all attributes from '%s'. It will be re-assimilated\n", filepath.Base(entry.Path))
|
||||
filepath.WalkDir(entry.Path, func(path string, d os.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("error walking path '%s': %w", path, err)
|
||||
}
|
||||
|
||||
// Remove all attributes from the file.
|
||||
if err := removeAttributes(path); err != nil {
|
||||
fmt.Printf("failed to remove attributes from '%s': %v", path, err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateOwnerIndexFile handles the logic of reading, modifying, and writing the MessagePack index file.
|
||||
func updateOwnerIndexFile(basePath string, targetID string) error {
|
||||
fmt.Printf(" Rewriting index file '%s'\n", basePath)
|
||||
|
||||
ownerID, err := xattr.Get(basePath, ownerIDAttrName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get owner ID from oldest entry '%s' to find index: %w", basePath, err)
|
||||
}
|
||||
|
||||
indexPath := filepath.Join(basePath, "../../indexes/by-user-id", string(ownerID)+".mpk")
|
||||
indexPath = filepath.Clean(indexPath)
|
||||
|
||||
// Read the MessagePack file
|
||||
fileData, err := os.ReadFile(indexPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return fmt.Errorf("index file does not exist, skipping update")
|
||||
}
|
||||
return fmt.Errorf("could not read index file: %w", err)
|
||||
}
|
||||
var indexMap map[string]string
|
||||
if err := msgpack.Unmarshal(fileData, &indexMap); err != nil {
|
||||
return fmt.Errorf("failed to parse MessagePack index file (is it corrupt?): %w", err)
|
||||
}
|
||||
|
||||
// Remove obsolete IDs from the map
|
||||
itemsRemoved := 0
|
||||
for id := range indexMap {
|
||||
if id != targetID {
|
||||
if _, ok := indexMap[id]; ok {
|
||||
delete(indexMap, id)
|
||||
itemsRemoved++
|
||||
fmt.Printf(" - Removing obsolete ID '%s' from index.\n", id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if itemsRemoved == 0 {
|
||||
fmt.Printf(" No obsolete IDs found in the index file. Nothing to change.\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write the data back to the file
|
||||
updatedData, err := msgpack.Marshal(&indexMap)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal updated index map: %w", err)
|
||||
}
|
||||
if err := os.WriteFile(indexPath, updatedData, 0644); err != nil {
|
||||
return fmt.Errorf("failed to write updated index file: %w", err)
|
||||
}
|
||||
|
||||
logSuccess("Successfully removed %d item(s) and saved index file.\n", itemsRemoved)
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeAttributes(path string) error {
|
||||
attrNames, err := xattr.List(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list attributes for '%s': %w", path, err)
|
||||
}
|
||||
|
||||
for _, attrName := range attrNames {
|
||||
if err := xattr.Remove(path, attrName); err != nil {
|
||||
return fmt.Errorf("failed to remove attribute '%s' from '%s': %w", attrName, path, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func logFailure(message string, args ...any) {
|
||||
spinner.StopFailMessage(fmt.Sprintf(message, args...))
|
||||
spinner.StopFail()
|
||||
spinner.Start()
|
||||
}
|
||||
|
||||
func logSuccess(message string, args ...any) {
|
||||
spinner.StopMessage(fmt.Sprintf(message, args...))
|
||||
spinner.Stop()
|
||||
spinner.Start()
|
||||
}
|
||||
25
vendor/github.com/theckman/yacspin/.gitignore
generated
vendored
Normal file
25
vendor/github.com/theckman/yacspin/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool
|
||||
*.out
|
||||
coverage.txt
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# README GIF generation files
|
||||
*.mov
|
||||
*.mp4
|
||||
*.gif
|
||||
*.prproj
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
71
vendor/github.com/theckman/yacspin/.golangci.yaml
generated
vendored
Normal file
71
vendor/github.com/theckman/yacspin/.golangci.yaml
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
run:
|
||||
tests: true
|
||||
|
||||
# all available settings of specific linters
|
||||
linters-settings:
|
||||
govet:
|
||||
# report about shadowed variables
|
||||
check-shadowing: true
|
||||
gofmt:
|
||||
# simplify code: gofmt with `-s` option, true by default
|
||||
simplify: true
|
||||
dupl:
|
||||
# tokens count to trigger issue, 150 by default
|
||||
threshold: 100
|
||||
goconst:
|
||||
# minimal length of string constant, 3 by default
|
||||
min-len: 3
|
||||
# minimal occurrences count to trigger, 3 by default
|
||||
min-occurrences: 3
|
||||
misspell:
|
||||
# Correct spellings using locale preferences for US or UK.
|
||||
# Default is to use a neutral variety of English.
|
||||
# Setting locale to US will correct the British spelling of 'colour' to 'color'.
|
||||
locale: US
|
||||
staticcheck:
|
||||
checks: [ "all" ]
|
||||
revive:
|
||||
confidence: 0.8
|
||||
ignore-generated-header: true
|
||||
rules:
|
||||
- name: context-keys-type
|
||||
- name: time-naming
|
||||
- name: var-declaration
|
||||
- name: unexported-return
|
||||
- name: errorf
|
||||
- name: blank-imports
|
||||
- name: context-as-argument
|
||||
- name: dot-imports
|
||||
- name: error-return
|
||||
- name: error-strings
|
||||
- name: error-naming
|
||||
- name: exported
|
||||
- name: increment-decrement
|
||||
- name: var-naming
|
||||
- name: package-comments
|
||||
- name: range
|
||||
- name: receiver-naming
|
||||
- name: indent-error-flow
|
||||
- name: superfluous-else
|
||||
- name: struct-tag
|
||||
- name: modifies-value-receiver
|
||||
- name: range-val-in-closure
|
||||
- name: range-val-address
|
||||
- name: atomic
|
||||
- name: empty-lines
|
||||
- name: early-return
|
||||
- name: useless-break
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- revive
|
||||
- govet
|
||||
- gosec
|
||||
- staticcheck
|
||||
- typecheck
|
||||
fast: false
|
||||
|
||||
issues:
|
||||
exclude-use-default: false
|
||||
exclude:
|
||||
- G104
|
||||
201
vendor/github.com/theckman/yacspin/LICENSE
generated
vendored
Normal file
201
vendor/github.com/theckman/yacspin/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
274
vendor/github.com/theckman/yacspin/README.md
generated
vendored
Normal file
274
vendor/github.com/theckman/yacspin/README.md
generated
vendored
Normal file
@@ -0,0 +1,274 @@
|
||||
# Yet Another CLi Spinner (for Go)
|
||||
[](https://github.com/theckman/yacspin/blob/master/LICENSE)
|
||||
[](https://godoc.org/github.com/theckman/yacspin)
|
||||
[](https://github.com/theckman/yacspin/releases)
|
||||
[](https://github.com/theckman/yacspin/actions/workflows/tests.yaml)
|
||||
[](https://goreportcard.com/report/github.com/theckman/yacspin)
|
||||
[](https://codecov.io/gh/theckman/yacspin)
|
||||
|
||||
Package `yacspin` provides yet another CLi spinner for Go, taking inspiration
|
||||
(and some utility code) from the https://github.com/briandowns/spinner project.
|
||||
Specifically `yacspin` borrows the default character sets, and color mappings to
|
||||
github.com/fatih/color colors, from that project.
|
||||
|
||||
## License
|
||||
Because this package adopts the spinner character sets from https://github.com/briandowns/spinner,
|
||||
this package is released under the Apache 2.0 License.
|
||||
|
||||
## Yet Another CLi Spinner?
|
||||
This project was created after it was realized that the most popular spinner
|
||||
library for Go had some limitations, that couldn't be fixed without a massive
|
||||
overhaul of the API.
|
||||
|
||||
The other spinner ties the ability to show updated messages to the spinner's
|
||||
animation, meaning you can't always show all the information you want to the end
|
||||
user without changing the animation speed. This means you need to trade off
|
||||
animation aesthetics to show "realtime" information. It was a goal to avoid this
|
||||
problem.
|
||||
|
||||
In addition, there were also some API design choices that have made it unsafe
|
||||
for concurrent use, which presents challenges when trying to update the text in
|
||||
the spinner while it's animating. This could result in undefined behavior due to
|
||||
data races.
|
||||
|
||||
There were also some variable-width spinners in that other project that did
|
||||
not render correctly. Because the width of the spinner animation would change,
|
||||
so would the position of the message on the screen. `yacspin` uses a dynamic
|
||||
width when animating, so your message should appear static relative to the
|
||||
animating spinner.
|
||||
|
||||
Finally, there was an interest in the spinner being able to represent a task, and to
|
||||
indicate whether it failed or was successful. This would have further compounded
|
||||
the API changes needed above to support in an intuitive way.
|
||||
|
||||
This project takes inspiration from that other project, and takes a new approach
|
||||
to address the challenges above.
|
||||
|
||||
## Features
|
||||
#### Provided Spinners
|
||||
There are over 90 spinners available in the `CharSets` package variable. They
|
||||
were borrowed from [github.com/briandowns/spinner](https://github.com/briandowns/spinner).
|
||||
There is a table with most of the spinners [at the bottom of this README](#Spinners).
|
||||
|
||||
#### Dynamic Width of Animation
|
||||
Because of how some spinners are animated, they may have different widths are
|
||||
different times in the animation. `yacspin` calculates the maximum width, and
|
||||
pads the animation to ensure the text's position on the screen doesn't change.
|
||||
This results in a smoother looking animation.
|
||||
|
||||
##### yacspin
|
||||

|
||||
|
||||
##### other spinners
|
||||

|
||||
|
||||
#### Success and Failure Results
|
||||
The spinner has both a `Stop()` and `StopFail()` method, which allows the
|
||||
spinner to result in a success message or a failure message. The messages,
|
||||
colors, and even the character used to denote success or failure are
|
||||
customizable in either the initial config or via the spinner's methods.
|
||||
|
||||
By doing this you can use a single `yacspin` spinner to display the status of a
|
||||
list of tasks being executed serially.
|
||||
|
||||
##### Stop
|
||||

|
||||
|
||||
##### StopFail
|
||||

|
||||
|
||||
#### Animation At End of Line
|
||||
The `SpinnerAtEnd` field of the `Config` struct allows you to specify whether
|
||||
the spinner is rendered at the end of the line instead of the beginning. The
|
||||
default value (`false`) results in the spinner being rendered at the beginning
|
||||
of the line.
|
||||
|
||||
#### Concurrency
|
||||
The spinner is safe for concurrent use, so you can update any of its settings
|
||||
via methods whether the spinner is stopped or is currently animating.
|
||||
|
||||
#### Live Updates
|
||||
Most spinners tie the ability to show new messages with the animation of the
|
||||
spinner. So if the spinner animates every 200ms, you can only show updated
|
||||
information every 200ms. If you wanted more frequent updates, you'd need to
|
||||
tradeoff the asthetics of the animation to display more data.
|
||||
|
||||
This spinner updates the printed information of the spinner immediately on
|
||||
change, without the animation updating. This allows you to use an animation
|
||||
speed that looks astheticaly pleasing, while also knowing the data presented to
|
||||
the user will be updated live.
|
||||
|
||||
You can see this in action in the following gif, where the filenames being
|
||||
uploaded are rendered independent of the spinner being animated:
|
||||
|
||||

|
||||
|
||||
#### Pausing for Updates
|
||||
Sometimes you want to change a few settings, and don't want the spinner to
|
||||
render your partially applied configuration. If your spinner is running, and you
|
||||
want to change a few configuration items via method calls, you can `Pause()` the
|
||||
spinner first. After making the changes you can call `Unpause()`, and it will
|
||||
continue rendering like normal with the newly applied configuration.
|
||||
|
||||
#### Supporting Non-Interactive (TTY) Output Targets
|
||||
`yacspin` also has native support for non-interactive (TTY) output targets. By
|
||||
default this is detected in the constructor, or can be overriden via the
|
||||
`TerminalMode` `Config` struct field. When detecting the application is not
|
||||
running withn a TTY session, the behavior of the spinner is different.
|
||||
|
||||
Specifically, when this is automatically detected the spinner no longer uses
|
||||
colors, disables the automatic spinner animation, and instead only animates the
|
||||
spinner when updating the message. In addition, each animation is rendered on a
|
||||
new line instead of overwriting the current line.
|
||||
|
||||
This should result in human-readable output without any changes needed by
|
||||
consumers, even when the system is writing to a non-TTY destination.
|
||||
|
||||
#### Manually Stepping Animation
|
||||
If you'd like to manually animate the spinner, you can do so by setting the
|
||||
`TerminalMode` to `ForceNoTTYMode | ForceSmartTerminalMode`. In this mode the
|
||||
spinner will still use colors and other text stylings, but the animation only
|
||||
happens when data is updated and on individual lines. You can accomplish this by
|
||||
calling the `Message()` method with the same used previously.
|
||||
|
||||
## Usage
|
||||
```
|
||||
go get github.com/theckman/yacspin
|
||||
```
|
||||
|
||||
Within the `yacspin` package there are some default spinners stored in the
|
||||
`yacspin.CharSets` variable, and you can also provide your own. There is also a
|
||||
list of known colors in the `yacspin.ValidColors` variable.
|
||||
|
||||
### Example
|
||||
|
||||
There are runnable examples in the [examples/](https://github.com/theckman/yacspin/tree/master/examples)
|
||||
directory, with one simple example and one more advanced one. Here is a quick
|
||||
snippet showing usage from a very high level, with error handling omitted:
|
||||
|
||||
```Go
|
||||
cfg := yacspin.Config{
|
||||
Frequency: 100 * time.Millisecond,
|
||||
CharSet: yacspin.CharSets[59],
|
||||
Suffix: " backing up database to S3",
|
||||
SuffixAutoColon: true,
|
||||
Message: "exporting data",
|
||||
StopCharacter: "✓",
|
||||
StopColors: []string{"fgGreen"},
|
||||
}
|
||||
|
||||
spinner, err := yacspin.New(cfg)
|
||||
// handle the error
|
||||
|
||||
err = spinner.Start()
|
||||
|
||||
// doing some work
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
spinner.Message("uploading data")
|
||||
|
||||
// upload...
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
err = spinner.Stop()
|
||||
```
|
||||
|
||||
## Spinners
|
||||
|
||||
The spinner animations below are recorded at a refresh frequency of 200ms. Some
|
||||
animations may look better at a different speed, so play around with the
|
||||
frequency until you find a value you find aesthetically pleasing.
|
||||
|
||||
yacspin.CharSets index | sample gif (Frequency: 200ms)
|
||||
-----------------------|------------------------------
|
||||
0 | 
|
||||
1 | 
|
||||
2 | 
|
||||
3 | 
|
||||
4 | 
|
||||
5 | 
|
||||
6 | 
|
||||
7 | 
|
||||
8 | 
|
||||
9 | 
|
||||
10 | 
|
||||
11 | 
|
||||
12 | 
|
||||
13 | 
|
||||
14 | 
|
||||
15 | 
|
||||
16 | 
|
||||
17 | 
|
||||
18 | 
|
||||
19 | 
|
||||
20 | 
|
||||
21 | 
|
||||
22 | 
|
||||
23 | 
|
||||
24 | 
|
||||
25 | 
|
||||
26 | 
|
||||
27 | 
|
||||
28 | 
|
||||
29 | 
|
||||
30 | 
|
||||
31 | 
|
||||
32 | 
|
||||
33 | 
|
||||
34 | 
|
||||
35 | 
|
||||
36 | 
|
||||
37 | 
|
||||
38 | 
|
||||
39 | 
|
||||
40 | 
|
||||
41 | 
|
||||
42 | 
|
||||
43 | 
|
||||
44 | 
|
||||
45 | 
|
||||
46 | 
|
||||
47 | 
|
||||
48 | 
|
||||
49 | 
|
||||
50 | 
|
||||
51 | 
|
||||
52 | 
|
||||
53 | 
|
||||
54 | 
|
||||
55 | 
|
||||
56 | 
|
||||
57 | 
|
||||
58 | 
|
||||
59 | 
|
||||
60 | 
|
||||
61 | 
|
||||
62 | 
|
||||
63 | 
|
||||
64 | 
|
||||
65 | 
|
||||
66 | 
|
||||
67 | 
|
||||
68 | 
|
||||
69 | 
|
||||
70 | 
|
||||
71 | 
|
||||
72 | 
|
||||
73 | 
|
||||
74 | 
|
||||
75 | 
|
||||
76 | 
|
||||
77 | 
|
||||
78 | 
|
||||
79 | 
|
||||
80 | 
|
||||
81 | 
|
||||
82 | 
|
||||
83 | 
|
||||
84 | 
|
||||
85 | 
|
||||
86 | 
|
||||
87 | 
|
||||
88 | 
|
||||
89 | 
|
||||
90 | 
|
||||
121
vendor/github.com/theckman/yacspin/character_sets.go
generated
vendored
Normal file
121
vendor/github.com/theckman/yacspin/character_sets.go
generated
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
// Copyright (c) 2021 Brian J. Downs
|
||||
// Copyright (c) 2019-2021 Tim Heckman
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Please see the LICENSE file for the copy of the Apache 2.0 License.
|
||||
//
|
||||
// This file was copied from: https://github.com/briandowns/spinner
|
||||
//
|
||||
// Modifications:
|
||||
//
|
||||
// - removed runtime generation of CharSets 37 and 38; made them literals
|
||||
// - fixed pipe spinner (32) animation, by adding missing frame
|
||||
|
||||
package yacspin
|
||||
|
||||
// CharSets contains the default character sets from
|
||||
// https://github.com/briandowns/spinner.
|
||||
var CharSets = map[int][]string{
|
||||
0: {"←", "↖", "↑", "↗", "→", "↘", "↓", "↙"},
|
||||
1: {"▁", "▃", "▄", "▅", "▆", "▇", "█", "▇", "▆", "▅", "▄", "▃", "▁"},
|
||||
2: {"▖", "▘", "▝", "▗"},
|
||||
3: {"┤", "┘", "┴", "└", "├", "┌", "┬", "┐"},
|
||||
4: {"◢", "◣", "◤", "◥"},
|
||||
5: {"◰", "◳", "◲", "◱"},
|
||||
6: {"◴", "◷", "◶", "◵"},
|
||||
7: {"◐", "◓", "◑", "◒"},
|
||||
8: {".", "o", "O", "@", "*"},
|
||||
9: {"|", "/", "-", "\\"},
|
||||
10: {"◡◡", "⊙⊙", "◠◠"},
|
||||
11: {"⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"},
|
||||
12: {">))'>", " >))'>", " >))'>", " >))'>", " >))'>", " <'((<", " <'((<", " <'((<"},
|
||||
13: {"⠁", "⠂", "⠄", "⡀", "⢀", "⠠", "⠐", "⠈"},
|
||||
14: {"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"},
|
||||
15: {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"},
|
||||
16: {"▉", "▊", "▋", "▌", "▍", "▎", "▏", "▎", "▍", "▌", "▋", "▊", "▉"},
|
||||
17: {"■", "□", "▪", "▫"},
|
||||
18: {"←", "↑", "→", "↓"},
|
||||
19: {"╫", "╪"},
|
||||
20: {"⇐", "⇖", "⇑", "⇗", "⇒", "⇘", "⇓", "⇙"},
|
||||
21: {"⠁", "⠁", "⠉", "⠙", "⠚", "⠒", "⠂", "⠂", "⠒", "⠲", "⠴", "⠤", "⠄", "⠄", "⠤", "⠠", "⠠", "⠤", "⠦", "⠖", "⠒", "⠐", "⠐", "⠒", "⠓", "⠋", "⠉", "⠈", "⠈"},
|
||||
22: {"⠈", "⠉", "⠋", "⠓", "⠒", "⠐", "⠐", "⠒", "⠖", "⠦", "⠤", "⠠", "⠠", "⠤", "⠦", "⠖", "⠒", "⠐", "⠐", "⠒", "⠓", "⠋", "⠉", "⠈"},
|
||||
23: {"⠁", "⠉", "⠙", "⠚", "⠒", "⠂", "⠂", "⠒", "⠲", "⠴", "⠤", "⠄", "⠄", "⠤", "⠴", "⠲", "⠒", "⠂", "⠂", "⠒", "⠚", "⠙", "⠉", "⠁"},
|
||||
24: {"⠋", "⠙", "⠚", "⠒", "⠂", "⠂", "⠒", "⠲", "⠴", "⠦", "⠖", "⠒", "⠐", "⠐", "⠒", "⠓", "⠋"},
|
||||
25: {"ヲ", "ァ", "ィ", "ゥ", "ェ", "ォ", "ャ", "ュ", "ョ", "ッ", "ア", "イ", "ウ", "エ", "オ", "カ", "キ", "ク", "ケ", "コ", "サ", "シ", "ス", "セ", "ソ", "タ", "チ", "ツ", "テ", "ト", "ナ", "ニ", "ヌ", "ネ", "ノ", "ハ", "ヒ", "フ", "ヘ", "ホ", "マ", "ミ", "ム", "メ", "モ", "ヤ", "ユ", "ヨ", "ラ", "リ", "ル", "レ", "ロ", "ワ", "ン"},
|
||||
26: {".", "..", "..."},
|
||||
27: {"▁", "▂", "▃", "▄", "▅", "▆", "▇", "█", "▉", "▊", "▋", "▌", "▍", "▎", "▏", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█", "▇", "▆", "▅", "▄", "▃", "▂", "▁"},
|
||||
28: {".", "o", "O", "°", "O", "o", "."},
|
||||
29: {"+", "x"},
|
||||
30: {"v", "<", "^", ">"},
|
||||
31: {">>--->", " >>--->", " >>--->", " >>--->", " >>--->", " <---<<", " <---<<", " <---<<", " <---<<", "<---<<"},
|
||||
32: {"|", "||", "|||", "||||", "|||||", "||||||", "|||||||", "||||||||", "|||||||", "||||||", "|||||", "||||", "|||", "||", "|"},
|
||||
33: {"[ ]", "[= ]", "[== ]", "[=== ]", "[==== ]", "[===== ]", "[====== ]", "[======= ]", "[======== ]", "[========= ]", "[==========]"},
|
||||
34: {"(*---------)", "(-*--------)", "(--*-------)", "(---*------)", "(----*-----)", "(-----*----)", "(------*---)", "(-------*--)", "(--------*-)", "(---------*)"},
|
||||
35: {"█▒▒▒▒▒▒▒▒▒", "███▒▒▒▒▒▒▒", "█████▒▒▒▒▒", "███████▒▒▒", "██████████"},
|
||||
36: {"[ ]", "[=> ]", "[===> ]", "[=====> ]", "[======> ]", "[========> ]", "[==========> ]", "[============> ]", "[==============> ]", "[================> ]", "[==================> ]", "[===================>]"},
|
||||
37: {"🕐", "🕑", "🕒", "🕓", "🕔", "🕕", "🕖", "🕗", "🕘", "🕙", "🕚", "🕛"}, // clock emoji: one per hour for hours 1~12
|
||||
38: {"🕐", "🕜", "🕑", "🕝", "🕒", "🕞", "🕓", "🕟", "🕔", "🕠", "🕕", "🕡", "🕖", "🕢", "🕗", "🕣", "🕘", "🕤", "🕙", "🕥", "🕚", "🕦", "🕛", "🕧"}, // clock emoji: one per half hour for hours 1~12
|
||||
39: {"🌍", "🌎", "🌏"},
|
||||
40: {"◜", "◝", "◞", "◟"},
|
||||
41: {"⬒", "⬔", "⬓", "⬕"},
|
||||
42: {"⬖", "⬘", "⬗", "⬙"},
|
||||
43: {"[>>> >]", "[]>>>> []", "[] >>>> []", "[] >>>> []", "[] >>>> []", "[] >>>>[]", "[>> >>]"},
|
||||
44: {"♠", "♣", "♥", "♦"},
|
||||
45: {"➞", "➟", "➠", "➡", "➠", "➟"},
|
||||
46: {" | ", ` \ `, "_ ", ` \ `, " | ", " / ", " _", " / "},
|
||||
47: {" . . . .", ". . . .", ". . . .", ". . . .", ". . . . ", ". . . . ."},
|
||||
48: {" | ", " / ", " _ ", ` \ `, " | ", ` \ `, " _ ", " / "},
|
||||
49: {"⎺", "⎻", "⎼", "⎽", "⎼", "⎻"},
|
||||
50: {"▹▹▹▹▹", "▸▹▹▹▹", "▹▸▹▹▹", "▹▹▸▹▹", "▹▹▹▸▹", "▹▹▹▹▸"},
|
||||
51: {"[ ]", "[ =]", "[ ==]", "[ ===]", "[====]", "[=== ]", "[== ]", "[= ]"},
|
||||
52: {"( ● )", "( ● )", "( ● )", "( ● )", "( ●)", "( ● )", "( ● )", "( ● )", "( ● )"},
|
||||
53: {"✶", "✸", "✹", "✺", "✹", "✷"},
|
||||
54: {"▐|\\____________▌", "▐_|\\___________▌", "▐__|\\__________▌", "▐___|\\_________▌", "▐____|\\________▌", "▐_____|\\_______▌", "▐______|\\______▌", "▐_______|\\_____▌", "▐________|\\____▌", "▐_________|\\___▌", "▐__________|\\__▌", "▐___________|\\_▌", "▐____________|\\▌", "▐____________/|▌", "▐___________/|_▌", "▐__________/|__▌", "▐_________/|___▌", "▐________/|____▌", "▐_______/|_____▌", "▐______/|______▌", "▐_____/|_______▌", "▐____/|________▌", "▐___/|_________▌", "▐__/|__________▌", "▐_/|___________▌", "▐/|____________▌"},
|
||||
55: {"▐⠂ ▌", "▐⠈ ▌", "▐ ⠂ ▌", "▐ ⠠ ▌", "▐ ⡀ ▌", "▐ ⠠ ▌", "▐ ⠂ ▌", "▐ ⠈ ▌", "▐ ⠂ ▌", "▐ ⠠ ▌", "▐ ⡀ ▌", "▐ ⠠ ▌", "▐ ⠂ ▌", "▐ ⠈ ▌", "▐ ⠂▌", "▐ ⠠▌", "▐ ⡀▌", "▐ ⠠ ▌", "▐ ⠂ ▌", "▐ ⠈ ▌", "▐ ⠂ ▌", "▐ ⠠ ▌", "▐ ⡀ ▌", "▐ ⠠ ▌", "▐ ⠂ ▌", "▐ ⠈ ▌", "▐ ⠂ ▌", "▐ ⠠ ▌", "▐ ⡀ ▌", "▐⠠ ▌"},
|
||||
56: {"¿", "?"},
|
||||
57: {"⢹", "⢺", "⢼", "⣸", "⣇", "⡧", "⡗", "⡏"},
|
||||
58: {"⢄", "⢂", "⢁", "⡁", "⡈", "⡐", "⡠"},
|
||||
59: {". ", ".. ", "...", " ..", " .", " "},
|
||||
60: {".", "o", "O", "°", "O", "o", "."},
|
||||
61: {"▓", "▒", "░"},
|
||||
62: {"▌", "▀", "▐", "▄"},
|
||||
63: {"⊶", "⊷"},
|
||||
64: {"▪", "▫"},
|
||||
65: {"□", "■"},
|
||||
66: {"▮", "▯"},
|
||||
67: {"-", "=", "≡"},
|
||||
68: {"d", "q", "p", "b"},
|
||||
69: {"∙∙∙", "●∙∙", "∙●∙", "∙∙●", "∙∙∙"},
|
||||
70: {"🌑 ", "🌒 ", "🌓 ", "🌔 ", "🌕 ", "🌖 ", "🌗 ", "🌘 "},
|
||||
71: {"☗", "☖"},
|
||||
72: {"⧇", "⧆"},
|
||||
73: {"◉", "◎"},
|
||||
74: {"㊂", "㊀", "㊁"},
|
||||
75: {"⦾", "⦿"},
|
||||
76: {"ဝ", "၀"},
|
||||
77: {"▌", "▀", "▐▄"},
|
||||
78: {"⠈⠁", "⠈⠑", "⠈⠱", "⠈⡱", "⢀⡱", "⢄⡱", "⢄⡱", "⢆⡱", "⢎⡱", "⢎⡰", "⢎⡠", "⢎⡀", "⢎⠁", "⠎⠁", "⠊⠁"},
|
||||
79: {"________", "-_______", "_-______", "__-_____", "___-____", "____-___", "_____-__", "______-_", "_______-", "________", "_______-", "______-_", "_____-__", "____-___", "___-____", "__-_____", "_-______", "-_______", "________"},
|
||||
80: {"|_______", "_/______", "__-_____", "___\\____", "____|___", "_____/__", "______-_", "_______\\", "_______|", "______\\_", "_____-__", "____/___", "___|____", "__\\_____", "_-______"},
|
||||
81: {"□", "◱", "◧", "▣", "■"},
|
||||
82: {"□", "◱", "▨", "▩", "■"},
|
||||
83: {"░", "▒", "▓", "█"},
|
||||
84: {"░", "█"},
|
||||
85: {"⚪", "⚫"},
|
||||
86: {"◯", "⬤"},
|
||||
87: {"▱", "▰"},
|
||||
88: {"➊", "➋", "➌", "➍", "➎", "➏", "➐", "➑", "➒", "➓"},
|
||||
89: {"½", "⅓", "⅔", "¼", "¾", "⅛", "⅜", "⅝", "⅞"},
|
||||
90: {"↞", "↟", "↠", "↡"},
|
||||
}
|
||||
174
vendor/github.com/theckman/yacspin/colors.go
generated
vendored
Normal file
174
vendor/github.com/theckman/yacspin/colors.go
generated
vendored
Normal file
@@ -0,0 +1,174 @@
|
||||
// This file is available under the Apache 2.0 License
|
||||
// This file was copied from: https://github.com/briandowns/spinner
|
||||
//
|
||||
// Please see the LICENSE file for the copy of the Apache 2.0 License.
|
||||
//
|
||||
// Modifications:
|
||||
//
|
||||
// - made validColors set map more idiomatic with an empty struct value
|
||||
// - added a function for creating color functions from color list
|
||||
|
||||
package yacspin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
// ValidColors holds the list of the strings that are mapped to
|
||||
// github.com/fatih/color color attributes. Any of these colors / attributes can
|
||||
// be used with the *Spinner type, and it should be reflected in the output.
|
||||
var ValidColors = map[string]struct{}{
|
||||
// default colors for backwards compatibility
|
||||
"black": {},
|
||||
"red": {},
|
||||
"green": {},
|
||||
"yellow": {},
|
||||
"blue": {},
|
||||
"magenta": {},
|
||||
"cyan": {},
|
||||
"white": {},
|
||||
|
||||
// attributes
|
||||
"reset": {},
|
||||
"bold": {},
|
||||
"faint": {},
|
||||
"italic": {},
|
||||
"underline": {},
|
||||
"blinkslow": {},
|
||||
"blinkrapid": {},
|
||||
"reversevideo": {},
|
||||
"concealed": {},
|
||||
"crossedout": {},
|
||||
|
||||
// foreground text
|
||||
"fgBlack": {},
|
||||
"fgRed": {},
|
||||
"fgGreen": {},
|
||||
"fgYellow": {},
|
||||
"fgBlue": {},
|
||||
"fgMagenta": {},
|
||||
"fgCyan": {},
|
||||
"fgWhite": {},
|
||||
|
||||
// foreground Hi-Intensity text
|
||||
"fgHiBlack": {},
|
||||
"fgHiRed": {},
|
||||
"fgHiGreen": {},
|
||||
"fgHiYellow": {},
|
||||
"fgHiBlue": {},
|
||||
"fgHiMagenta": {},
|
||||
"fgHiCyan": {},
|
||||
"fgHiWhite": {},
|
||||
|
||||
// background text
|
||||
"bgBlack": {},
|
||||
"bgRed": {},
|
||||
"bgGreen": {},
|
||||
"bgYellow": {},
|
||||
"bgBlue": {},
|
||||
"bgMagenta": {},
|
||||
"bgCyan": {},
|
||||
"bgWhite": {},
|
||||
|
||||
// background Hi-Intensity text
|
||||
"bgHiBlack": {},
|
||||
"bgHiRed": {},
|
||||
"bgHiGreen": {},
|
||||
"bgHiYellow": {},
|
||||
"bgHiBlue": {},
|
||||
"bgHiMagenta": {},
|
||||
"bgHiCyan": {},
|
||||
"bgHiWhite": {},
|
||||
}
|
||||
|
||||
// returns a valid color's foreground text color attribute
|
||||
var colorAttributeMap = map[string]color.Attribute{
|
||||
// default colors for backwards compatibility
|
||||
"black": color.FgBlack,
|
||||
"red": color.FgRed,
|
||||
"green": color.FgGreen,
|
||||
"yellow": color.FgYellow,
|
||||
"blue": color.FgBlue,
|
||||
"magenta": color.FgMagenta,
|
||||
"cyan": color.FgCyan,
|
||||
"white": color.FgWhite,
|
||||
|
||||
// attributes
|
||||
"reset": color.Reset,
|
||||
"bold": color.Bold,
|
||||
"faint": color.Faint,
|
||||
"italic": color.Italic,
|
||||
"underline": color.Underline,
|
||||
"blinkslow": color.BlinkSlow,
|
||||
"blinkrapid": color.BlinkRapid,
|
||||
"reversevideo": color.ReverseVideo,
|
||||
"concealed": color.Concealed,
|
||||
"crossedout": color.CrossedOut,
|
||||
|
||||
// foreground text colors
|
||||
"fgBlack": color.FgBlack,
|
||||
"fgRed": color.FgRed,
|
||||
"fgGreen": color.FgGreen,
|
||||
"fgYellow": color.FgYellow,
|
||||
"fgBlue": color.FgBlue,
|
||||
"fgMagenta": color.FgMagenta,
|
||||
"fgCyan": color.FgCyan,
|
||||
"fgWhite": color.FgWhite,
|
||||
|
||||
// foreground Hi-Intensity text colors
|
||||
"fgHiBlack": color.FgHiBlack,
|
||||
"fgHiRed": color.FgHiRed,
|
||||
"fgHiGreen": color.FgHiGreen,
|
||||
"fgHiYellow": color.FgHiYellow,
|
||||
"fgHiBlue": color.FgHiBlue,
|
||||
"fgHiMagenta": color.FgHiMagenta,
|
||||
"fgHiCyan": color.FgHiCyan,
|
||||
"fgHiWhite": color.FgHiWhite,
|
||||
|
||||
// background text colors
|
||||
"bgBlack": color.BgBlack,
|
||||
"bgRed": color.BgRed,
|
||||
"bgGreen": color.BgGreen,
|
||||
"bgYellow": color.BgYellow,
|
||||
"bgBlue": color.BgBlue,
|
||||
"bgMagenta": color.BgMagenta,
|
||||
"bgCyan": color.BgCyan,
|
||||
"bgWhite": color.BgWhite,
|
||||
|
||||
// background Hi-Intensity text colors
|
||||
"bgHiBlack": color.BgHiBlack,
|
||||
"bgHiRed": color.BgHiRed,
|
||||
"bgHiGreen": color.BgHiGreen,
|
||||
"bgHiYellow": color.BgHiYellow,
|
||||
"bgHiBlue": color.BgHiBlue,
|
||||
"bgHiMagenta": color.BgHiMagenta,
|
||||
"bgHiCyan": color.BgHiCyan,
|
||||
"bgHiWhite": color.BgHiWhite,
|
||||
}
|
||||
|
||||
// validColor will make sure the given color is actually allowed
|
||||
func validColor(c string) bool {
|
||||
_, ok := ValidColors[c]
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
func colorFunc(colors ...string) (func(format string, a ...interface{}) string, error) {
|
||||
if len(colors) == 0 {
|
||||
return fmt.Sprintf, nil
|
||||
}
|
||||
|
||||
attrib := make([]color.Attribute, len(colors))
|
||||
|
||||
for i, color := range colors {
|
||||
if !validColor(color) {
|
||||
return nil, fmt.Errorf("%s is not a valid color", color)
|
||||
}
|
||||
|
||||
attrib[i] = colorAttributeMap[color]
|
||||
}
|
||||
|
||||
return color.New(attrib...).SprintfFunc(), nil
|
||||
}
|
||||
1191
vendor/github.com/theckman/yacspin/spinner.go
generated
vendored
Normal file
1191
vendor/github.com/theckman/yacspin/spinner.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
3
vendor/modules.txt
vendored
3
vendor/modules.txt
vendored
@@ -1849,6 +1849,9 @@ github.com/tchap/go-patricia/v2/patricia
|
||||
## explicit
|
||||
github.com/test-go/testify/mock
|
||||
github.com/test-go/testify/require
|
||||
# github.com/theckman/yacspin v0.13.12
|
||||
## explicit; go 1.17
|
||||
github.com/theckman/yacspin
|
||||
# github.com/thejerf/suture/v4 v4.0.6
|
||||
## explicit; go 1.9
|
||||
github.com/thejerf/suture/v4
|
||||
|
||||
Reference in New Issue
Block a user