Adding upstream version 0.28.1.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
88f1d47ab6
commit
e28c88ef14
933 changed files with 194711 additions and 0 deletions
65
tools/osutils/cmd.go
Normal file
65
tools/osutils/cmd.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
package osutils
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/go-ozzo/ozzo-validation/v4/is"
|
||||
)
|
||||
|
||||
// LaunchURL attempts to open the provided url in the user's default browser.
|
||||
//
|
||||
// It is platform dependent and it uses:
|
||||
// - "open" on macOS
|
||||
// - "rundll32" on Windows
|
||||
// - "xdg-open" on everything else (Linux, FreeBSD, etc.)
|
||||
func LaunchURL(url string) error {
|
||||
if err := is.URL.Validate(url); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
return exec.Command("open", url).Start()
|
||||
case "windows":
|
||||
// not sure if this is the best command but seems to be the most reliable based on the comments in
|
||||
// https://stackoverflow.com/questions/3739327/launching-a-website-via-the-windows-commandline#answer-49115945
|
||||
return exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
|
||||
default:
|
||||
return exec.Command("xdg-open", url).Start()
|
||||
}
|
||||
}
|
||||
|
||||
// YesNoPrompt performs a console prompt that asks the user for Yes/No answer.
|
||||
//
|
||||
// If the user just press Enter (aka. doesn't type anything) it returns the fallback value.
|
||||
func YesNoPrompt(message string, fallback bool) bool {
|
||||
options := "Y/n"
|
||||
if !fallback {
|
||||
options = "y/N"
|
||||
}
|
||||
|
||||
r := bufio.NewReader(os.Stdin)
|
||||
|
||||
var s string
|
||||
for {
|
||||
fmt.Fprintf(os.Stderr, "%s (%s) ", message, options)
|
||||
|
||||
s, _ = r.ReadString('\n')
|
||||
|
||||
s = strings.ToLower(strings.TrimSpace(s))
|
||||
|
||||
switch s {
|
||||
case "":
|
||||
return fallback
|
||||
case "y", "yes":
|
||||
return true
|
||||
case "n", "no":
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
66
tools/osutils/cmd_test.go
Normal file
66
tools/osutils/cmd_test.go
Normal file
|
@ -0,0 +1,66 @@
|
|||
package osutils_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/pocketbase/pocketbase/tools/osutils"
|
||||
)
|
||||
|
||||
func TestYesNoPrompt(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
stdin string
|
||||
fallback bool
|
||||
expected bool
|
||||
}{
|
||||
{"", false, false},
|
||||
{"", true, true},
|
||||
|
||||
// yes
|
||||
{"y", false, true},
|
||||
{"Y", false, true},
|
||||
{"Yes", false, true},
|
||||
{"yes", false, true},
|
||||
|
||||
// no
|
||||
{"n", true, false},
|
||||
{"N", true, false},
|
||||
{"No", true, false},
|
||||
{"no", true, false},
|
||||
|
||||
// invalid -> no/yes
|
||||
{"invalid|no", true, false},
|
||||
{"invalid|yes", false, true},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(fmt.Sprintf("%s_%v", s.stdin, s.fallback), func(t *testing.T) {
|
||||
stdinread, stdinwrite, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
parts := strings.Split(s.stdin, "|")
|
||||
for _, p := range parts {
|
||||
if _, err := stdinwrite.WriteString(p + "\n"); err != nil {
|
||||
t.Fatalf("Failed to write test stdin part %q: %v", p, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err = stdinwrite.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer func(oldStdin *os.File) { os.Stdin = oldStdin }(os.Stdin)
|
||||
os.Stdin = stdinread
|
||||
|
||||
result := osutils.YesNoPrompt("test", s.fallback)
|
||||
|
||||
if result != s.expected {
|
||||
t.Fatalf("Expected %v, got %v", s.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
79
tools/osutils/dir.go
Normal file
79
tools/osutils/dir.go
Normal file
|
@ -0,0 +1,79 @@
|
|||
package osutils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pocketbase/pocketbase/tools/list"
|
||||
)
|
||||
|
||||
// MoveDirContent moves the src dir content, that is not listed in the exclude list,
|
||||
// to dest dir (it will be created if missing).
|
||||
//
|
||||
// The rootExclude argument is used to specify a list of src root entries to exclude.
|
||||
//
|
||||
// Note that this method doesn't delete the old src dir.
|
||||
//
|
||||
// It is an alternative to os.Rename() for the cases where we can't
|
||||
// rename/delete the src dir (see https://github.com/pocketbase/pocketbase/issues/2519).
|
||||
func MoveDirContent(src string, dest string, rootExclude ...string) error {
|
||||
entries, err := os.ReadDir(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// make sure that the dest dir exist
|
||||
manuallyCreatedDestDir := false
|
||||
if _, err := os.Stat(dest); err != nil {
|
||||
if err := os.Mkdir(dest, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
manuallyCreatedDestDir = true
|
||||
}
|
||||
|
||||
moved := map[string]string{}
|
||||
|
||||
tryRollback := func() []error {
|
||||
errs := []error{}
|
||||
|
||||
for old, new := range moved {
|
||||
if err := os.Rename(new, old); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
// try to delete manually the created dest dir if all moved files were restored
|
||||
if manuallyCreatedDestDir && len(errs) == 0 {
|
||||
if err := os.Remove(dest); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
basename := entry.Name()
|
||||
|
||||
if list.ExistInSlice(basename, rootExclude) {
|
||||
continue
|
||||
}
|
||||
|
||||
old := filepath.Join(src, basename)
|
||||
new := filepath.Join(dest, basename)
|
||||
|
||||
if err := os.Rename(old, new); err != nil {
|
||||
if errs := tryRollback(); len(errs) > 0 {
|
||||
errs = append(errs, err)
|
||||
err = errors.Join(errs...)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
moved[old] = new
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
142
tools/osutils/dir_test.go
Normal file
142
tools/osutils/dir_test.go
Normal file
|
@ -0,0 +1,142 @@
|
|||
package osutils_test
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/pocketbase/pocketbase/tools/list"
|
||||
"github.com/pocketbase/pocketbase/tools/osutils"
|
||||
"github.com/pocketbase/pocketbase/tools/security"
|
||||
)
|
||||
|
||||
func TestMoveDirContent(t *testing.T) {
|
||||
testDir := createTestDir(t)
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
exclude := []string{
|
||||
"missing",
|
||||
"test2",
|
||||
"b",
|
||||
}
|
||||
|
||||
// missing dest path
|
||||
// ---
|
||||
dir1 := filepath.Join(filepath.Dir(testDir), "a", "b", "c", "d", "_pb_move_dir_content_test_"+security.PseudorandomString(4))
|
||||
defer os.RemoveAll(dir1)
|
||||
|
||||
if err := osutils.MoveDirContent(testDir, dir1, exclude...); err == nil {
|
||||
t.Fatal("Expected path error, got nil")
|
||||
}
|
||||
|
||||
// existing parent dir
|
||||
// ---
|
||||
dir2 := filepath.Join(filepath.Dir(testDir), "_pb_move_dir_content_test_"+security.PseudorandomString(4))
|
||||
defer os.RemoveAll(dir2)
|
||||
|
||||
if err := osutils.MoveDirContent(testDir, dir2, exclude...); err != nil {
|
||||
t.Fatalf("Expected dir2 to be created, got error: %v", err)
|
||||
}
|
||||
|
||||
// find all files
|
||||
files := []string{}
|
||||
filepath.WalkDir(dir2, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
files = append(files, path)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
expectedFiles := []string{
|
||||
filepath.Join(dir2, "test1"),
|
||||
filepath.Join(dir2, "a", "a1"),
|
||||
filepath.Join(dir2, "a", "a2"),
|
||||
}
|
||||
|
||||
if len(files) != len(expectedFiles) {
|
||||
t.Fatalf("Expected %d files, got %d: \n%v", len(expectedFiles), len(files), files)
|
||||
}
|
||||
|
||||
for _, expected := range expectedFiles {
|
||||
if !list.ExistInSlice(expected, files) {
|
||||
t.Fatalf("Missing expected file %q in \n%v", expected, files)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
// note: make sure to call os.RemoveAll(dir) after you are done
|
||||
// working with the created test dir.
|
||||
func createTestDir(t *testing.T) string {
|
||||
dir, err := os.MkdirTemp(os.TempDir(), "test_dir")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// create sub directories
|
||||
if err := os.MkdirAll(filepath.Join(dir, "a"), os.ModePerm); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Join(dir, "b"), os.ModePerm); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
{
|
||||
f, err := os.OpenFile(filepath.Join(dir, "test1"), os.O_WRONLY|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
|
||||
{
|
||||
f, err := os.OpenFile(filepath.Join(dir, "test2"), os.O_WRONLY|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
|
||||
{
|
||||
f, err := os.OpenFile(filepath.Join(dir, "a/a1"), os.O_WRONLY|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
|
||||
{
|
||||
f, err := os.OpenFile(filepath.Join(dir, "a/a2"), os.O_WRONLY|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
|
||||
{
|
||||
f, err := os.OpenFile(filepath.Join(dir, "b/b2"), os.O_WRONLY|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
|
||||
{
|
||||
f, err := os.OpenFile(filepath.Join(dir, "b/b2"), os.O_WRONLY|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
|
||||
return dir
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue