5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-03 06:39:30 +08:00

use mewn for templates

massively improve template handling
This commit is contained in:
Lea Anthony 2019-03-02 12:54:10 +11:00
parent c20aabc8f8
commit c180d7dccb
No known key found for this signature in database
GPG Key ID: 33DAF7BB90A58405
10 changed files with 226 additions and 315 deletions

30
cmd/cmd-mewn.go Normal file

File diff suppressed because one or more lines are too long

View File

@ -41,6 +41,12 @@ func (fs *FSHelper) FileExists(path string) bool {
return fi.Mode().IsRegular() return fi.Mode().IsRegular()
} }
func (fs *FSHelper) CreateFile(filename string, data []byte) error {
// Ensure directory exists
fs.MkDirs(filepath.Dir(filename))
return ioutil.WriteFile(filename, data, 0644)
}
// MkDirs creates the given nested directories. // MkDirs creates the given nested directories.
// Returns error on failure // Returns error on failure
func (fs *FSHelper) MkDirs(fullPath string, mode ...os.FileMode) error { func (fs *FSHelper) MkDirs(fullPath string, mode ...os.FileMode) error {

View File

@ -14,6 +14,8 @@ import (
"github.com/leaanthony/spinner" "github.com/leaanthony/spinner"
) )
var fs = NewFSHelper()
// ValidateFrontendConfig checks if the frontend config is valid // ValidateFrontendConfig checks if the frontend config is valid
func ValidateFrontendConfig(projectOptions *ProjectOptions) error { func ValidateFrontendConfig(projectOptions *ProjectOptions) error {
if projectOptions.FrontEnd.Dir == "" { if projectOptions.FrontEnd.Dir == "" {

View File

@ -7,6 +7,8 @@ import (
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings" "strings"
"github.com/leaanthony/slicer"
) )
type author struct { type author struct {
@ -47,15 +49,11 @@ func NewProjectHelper() *ProjectHelper {
// GenerateProject generates a new project using the options given // GenerateProject generates a new project using the options given
func (ph *ProjectHelper) GenerateProject(projectOptions *ProjectOptions) error { func (ph *ProjectHelper) GenerateProject(projectOptions *ProjectOptions) error {
fs := NewFSHelper() // exists := ph.templates.TemplateExists(projectOptions.Template)
exists, err := ph.templates.TemplateExists(projectOptions.Template)
if err != nil {
return err
}
if !exists { // if !exists {
return fmt.Errorf("template '%s' is invalid", projectOptions.Template) // return fmt.Errorf("template '%s' is invalid", projectOptions.Template)
} // }
// Calculate project path // Calculate project path
projectPath, err := filepath.Abs(projectOptions.OutputDirectory) projectPath, err := filepath.Abs(projectOptions.OutputDirectory)
@ -63,6 +61,8 @@ func (ph *ProjectHelper) GenerateProject(projectOptions *ProjectOptions) error {
return err return err
} }
_ = projectPath
if fs.DirExists(projectPath) { if fs.DirExists(projectPath) {
return fmt.Errorf("directory '%s' already exists", projectPath) return fmt.Errorf("directory '%s' already exists", projectPath)
} }
@ -121,7 +121,6 @@ func (ph *ProjectHelper) NewProjectOptions() *ProjectOptions {
system: NewSystemHelper(), system: NewSystemHelper(),
log: NewLogger(), log: NewLogger(),
templates: NewTemplateHelper(), templates: NewTemplateHelper(),
templateNameMap: make(map[string]string),
Author: &author{}, Author: &author{},
} }
@ -150,12 +149,12 @@ type ProjectOptions struct {
system *SystemHelper system *SystemHelper
log *Logger log *Logger
templates *TemplateHelper templates *TemplateHelper
templateNameMap map[string]string // Converts template prompt text to template name selectedTemplate *TemplateDetails
} }
// Defaults sets the default project template // Defaults sets the default project template
func (po *ProjectOptions) Defaults() { func (po *ProjectOptions) Defaults() {
po.Template = "basic" po.Template = "vuebasic"
} }
// PromptForInputs asks the user to input project details // PromptForInputs asks the user to input project details
@ -170,48 +169,31 @@ func (po *ProjectOptions) PromptForInputs() error {
return err return err
} }
templateDetails, err := po.templates.GetTemplateDetails() // Process Templates
if err != nil { templateList := slicer.Interface()
return err options := slicer.String()
for _, templateDetails := range po.templates.TemplateList.details {
templateList.Add(templateDetails)
options.Add(fmt.Sprintf("%s - %s", templateDetails.Metadata.Name, templateDetails.Metadata.ShortDescription))
} }
templates := []string{} templateIndex := 0
// Add a Custom Template
// templates = append(templates, "Custom - Choose your own CSS framework") if len(options.AsSlice()) > 1 {
for templateName, templateDetails := range templateDetails { templateIndex = PromptSelection("Please select a template", options.AsSlice(), 0)
templateText := templateName
// Check if metadata json exists
if templateDetails.Metadata != nil {
shortdescription := templateDetails.Metadata["shortdescription"]
if shortdescription != "" {
templateText += " - " + shortdescription.(string)
}
}
templates = append(templates, templateText)
po.templateNameMap[templateText] = templateName
} }
if po.Template != "" { // After selection do this....
if _, ok := templateDetails[po.Template]; !ok { po.selectedTemplate = templateList.AsSlice()[templateIndex].(*TemplateDetails)
po.log.Error("Template '%s' invalid.", po.Template)
templateSelected := PromptSelection("Select template", templates)
po.Template = templates[templateSelected]
}
} else {
templateSelected := PromptSelection("Select template", templates)
po.Template = templates[templateSelected]
}
// Setup NPM Project name // Setup NPM Project name
po.NPMProjectName = strings.ToLower(strings.Replace(po.Name, " ", "_", -1)) po.NPMProjectName = strings.ToLower(strings.Replace(po.Name, " ", "_", -1))
// Fix template name // Fix template name
if po.templateNameMap[po.Template] != "" { po.Template = strings.Split(po.selectedTemplate.Path, "/")[0]
po.Template = po.templateNameMap[po.Template]
}
// Populate template details // // Populate template details
templateMetadata := templateDetails[po.Template].Metadata templateMetadata := po.selectedTemplate.Metadata
err = processTemplateMetadata(templateMetadata, po) err = processTemplateMetadata(templateMetadata, po)
if err != nil { if err != nil {
@ -299,36 +281,36 @@ func processBinaryName(po *ProjectOptions) {
fmt.Println("Output binary Name: " + po.BinaryName) fmt.Println("Output binary Name: " + po.BinaryName)
} }
func processTemplateMetadata(templateMetadata map[string]interface{}, po *ProjectOptions) error { func processTemplateMetadata(templateMetadata *TemplateMetadata, po *ProjectOptions) error {
if templateMetadata["frontenddir"] != nil { if templateMetadata.FrontendDir != "" {
po.FrontEnd = &frontend{} po.FrontEnd = &frontend{}
po.FrontEnd.Dir = templateMetadata["frontenddir"].(string) po.FrontEnd.Dir = templateMetadata.FrontendDir
} }
if templateMetadata["install"] != nil { if templateMetadata.Install != "" {
if po.FrontEnd == nil { if po.FrontEnd == nil {
return fmt.Errorf("install set in template metadata but not frontenddir") return fmt.Errorf("install set in template metadata but not frontenddir")
} }
po.FrontEnd.Install = templateMetadata["install"].(string) po.FrontEnd.Install = templateMetadata.Install
} }
if templateMetadata["build"] != nil { if templateMetadata.Build != "" {
if po.FrontEnd == nil { if po.FrontEnd == nil {
return fmt.Errorf("build set in template metadata but not frontenddir") return fmt.Errorf("build set in template metadata but not frontenddir")
} }
po.FrontEnd.Build = templateMetadata["build"].(string) po.FrontEnd.Build = templateMetadata.Build
} }
if templateMetadata["bridge"] != nil { if templateMetadata.Bridge != "" {
if po.FrontEnd == nil { if po.FrontEnd == nil {
return fmt.Errorf("bridge set in template metadata but not frontenddir") return fmt.Errorf("bridge set in template metadata but not frontenddir")
} }
po.FrontEnd.Bridge = templateMetadata["bridge"].(string) po.FrontEnd.Bridge = templateMetadata.Bridge
} }
if templateMetadata["serve"] != nil { if templateMetadata.Serve != "" {
if po.FrontEnd == nil { if po.FrontEnd == nil {
return fmt.Errorf("serve set in template metadata but not frontenddir") return fmt.Errorf("serve set in template metadata but not frontenddir")
} }
po.FrontEnd.Serve = templateMetadata["serve"].(string) po.FrontEnd.Serve = templateMetadata.Serve
} }
return nil return nil
} }

View File

@ -34,18 +34,29 @@ func PromptRequired(question string, defaultValue ...string) string {
} }
// PromptSelection asks the user to choose an option // PromptSelection asks the user to choose an option
func PromptSelection(question string, options []string) int { func PromptSelection(question string, options []string, optionalDefaultValue ...int) int {
defaultValue := -1
message := "Please choose an option"
fmt.Println(question + ":") fmt.Println(question + ":")
if len(optionalDefaultValue) > 0 {
defaultValue = optionalDefaultValue[0] + 1
message = fmt.Sprintf("%s [%d]", message, defaultValue)
}
for index, option := range options { for index, option := range options {
fmt.Printf(" %d: %s\n", index+1, option) fmt.Printf(" %d: %s\n", index+1, option)
} }
fmt.Println()
selectedValue := -1 selectedValue := -1
for { for {
choice := Prompt("Please choose an option") choice := Prompt(message)
if choice == "" && defaultValue > -1 {
selectedValue = defaultValue - 1
break
}
// index // index
number, err := strconv.Atoi(choice) number, err := strconv.Atoi(choice)

View File

@ -3,112 +3,95 @@ package cmd
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt" "log"
"io/ioutil"
"os"
"path"
"path/filepath" "path/filepath"
"runtime" "regexp"
"strings" "strings"
"text/template" "text/template"
mewn "github.com/leaanthony/mewn"
mewnlib "github.com/leaanthony/mewn/lib"
"github.com/leaanthony/slicer"
) )
const templateSuffix = ".template" // TemplateMetadata holds all the metadata for a Wails template
type TemplateMetadata struct {
// TemplateHelper helps with creating projects Name string `json:"name"`
type TemplateHelper struct { ShortDescription string `json:"shortdescription"`
system *SystemHelper Description string `json:"description"`
fs *FSHelper Install string `json:"install"`
templateDir string Build string `json:"build"`
// templates map[string]string Author string `json:"author"`
templateSuffix string Created string `json:"created"`
metadataFilename string FrontendDir string `json:"frontenddir"`
Serve string `json:"serve"`
Bridge string `json:"bridge"`
} }
// Template defines a single template // TemplateDetails holds information about a specific template
type TemplateDetails struct {
BasePath string
Path string
Metadata *TemplateMetadata
}
// TemplateList is a list of available templates
type TemplateList struct {
details map[string]*TemplateDetails
}
// NewTemplateList creates a new TemplateList object
func NewTemplateList(filenames *mewnlib.FileGroup) *TemplateList {
// Iterate each template and store information
result := &TemplateList{details: make(map[string]*TemplateDetails)}
entries := slicer.String()
entries.AddSlice(filenames.Entries())
// Find all template.json files
metadataFiles := entries.Filter(func(filename string) bool {
match, _ := regexp.MatchString("(.)+template.json$", filename)
return match
})
// Load each metadata file
metadataFiles.Each(func(filename string) {
fileData := filenames.Bytes(filename)
var metadata TemplateMetadata
err := json.Unmarshal(fileData, &metadata)
if err != nil {
log.Fatalf("corrupt metadata for template: %s", filename)
}
path := strings.Split(filename, string(filepath.Separator))[0]
thisTemplate := &TemplateDetails{Path: path, Metadata: &metadata}
result.details[filename] = thisTemplate
})
return result
}
// Template holds details about a Wails template
type Template struct { type Template struct {
Name string Name string
Dir string Path string
Metadata map[string]interface{} Description string
}
// TemplateHelper is a utility object to help with processing templates
type TemplateHelper struct {
TemplateList *TemplateList
Files *mewnlib.FileGroup
} }
// NewTemplateHelper creates a new template helper // NewTemplateHelper creates a new template helper
func NewTemplateHelper() *TemplateHelper { func NewTemplateHelper() *TemplateHelper {
result := TemplateHelper{ files := mewn.Group("./templates")
system: NewSystemHelper(),
fs: NewFSHelper(),
templateSuffix: ".template",
metadataFilename: "template.json",
}
// Calculate template base dir
_, filename, _, _ := runtime.Caller(1)
result.templateDir = filepath.Join(path.Dir(filename), "templates")
// result.templateDir = filepath.Join(result.system.homeDir, "go", "src", "github.com", "wailsapp", "wails", "cmd", "templates")
return &result
}
// GetTemplateNames returns a map of all available templates return &TemplateHelper{
func (t *TemplateHelper) GetTemplateNames() (map[string]string, error) { TemplateList: NewTemplateList(files),
templateDirs, err := t.fs.GetSubdirs(t.templateDir) Files: files,
if err != nil {
return nil, err
} }
return templateDirs, nil
}
// GetTemplateDetails returns a map of Template structs containing details
// of the found templates
func (t *TemplateHelper) GetTemplateDetails() (map[string]*Template, error) {
templateDirs, err := t.fs.GetSubdirs(t.templateDir)
if err != nil {
return nil, err
}
result := make(map[string]*Template)
for name, dir := range templateDirs {
result[name] = &Template{
Dir: dir,
}
metadata, err := t.LoadMetadata(dir)
if err != nil {
return nil, err
}
result[name].Metadata = metadata
if metadata["name"] != nil {
result[name].Name = metadata["name"].(string)
} else {
// Ignore bad templates?
result[name] = nil
}
}
return result, nil
}
// LoadMetadata loads the template's 'metadata.json' file
func (t *TemplateHelper) LoadMetadata(dir string) (map[string]interface{}, error) {
templateFile := filepath.Join(dir, t.metadataFilename)
result := make(map[string]interface{})
if !t.fs.FileExists(templateFile) {
return nil, nil
}
rawJSON, err := ioutil.ReadFile(templateFile)
if err != nil {
return nil, err
}
err = json.Unmarshal(rawJSON, &result)
return result, err
}
// TemplateExists returns true if the given template name exists
func (t *TemplateHelper) TemplateExists(templateName string) (bool, error) {
templates, err := t.GetTemplateNames()
if err != nil {
return false, err
}
_, exists := templates[templateName]
return exists, nil
} }
// InstallTemplate installs the template given in the project options to the // InstallTemplate installs the template given in the project options to the
@ -116,162 +99,56 @@ func (t *TemplateHelper) TemplateExists(templateName string) (bool, error) {
func (t *TemplateHelper) InstallTemplate(projectPath string, projectOptions *ProjectOptions) error { func (t *TemplateHelper) InstallTemplate(projectPath string, projectOptions *ProjectOptions) error {
// Get template files // Get template files
template, err := t.getTemplateFiles(projectOptions.Template) templatePath := projectOptions.selectedTemplate.Path
if err != nil {
return err
}
// Copy files to target templateFilenames := slicer.String()
err = template.Install(projectPath, projectOptions) templateFilenames.AddSlice(projectOptions.templates.Files.Entries())
if err != nil {
return err
}
return nil templateJSONFilename := filepath.Join(templatePath, "template.json")
}
// templateFiles categorises files found in a template templateFiles := templateFilenames.Filter(func(filename string) bool {
type templateFiles struct { return strings.HasPrefix(filename, templatePath) && filename != templateJSONFilename
BaseDir string
StandardFiles []string
Templates []string
Dirs []string
}
// newTemplateFiles returns a new TemplateFiles struct
func (t *TemplateHelper) newTemplateFiles(dir string) *templateFiles {
pathsep := string(os.PathSeparator)
// Ensure base directory has trailing slash
if !strings.HasSuffix(dir, pathsep) {
dir = dir + pathsep
}
return &templateFiles{
BaseDir: dir,
}
}
// AddStandardFile adds the given file to the list of standard files
func (t *templateFiles) AddStandardFile(filename string) {
localPath := strings.TrimPrefix(filename, t.BaseDir)
t.StandardFiles = append(t.StandardFiles, localPath)
}
// AddTemplate adds the given file to the list of template files
func (t *templateFiles) AddTemplate(filename string) {
localPath := strings.TrimPrefix(filename, t.BaseDir)
t.Templates = append(t.Templates, localPath)
}
// AddDir adds the given directory to the list of template dirs
func (t *templateFiles) AddDir(dir string) {
localPath := strings.TrimPrefix(dir, t.BaseDir)
t.Dirs = append(t.Dirs, localPath)
}
// getTemplateFiles returns a struct categorising files in
// the template directory
func (t *TemplateHelper) getTemplateFiles(templateName string) (*templateFiles, error) {
templates, err := t.GetTemplateNames()
if err != nil {
return nil, err
}
templateDir := templates[templateName]
result := t.newTemplateFiles(templateDir)
var localPath string
err = filepath.Walk(templateDir, func(dir string, info os.FileInfo, err error) error {
if dir == templateDir {
return nil
}
if err != nil {
return err
}
// Don't copy template metadata
localPath = strings.TrimPrefix(dir, templateDir+string(filepath.Separator))
if localPath == t.metadataFilename {
return nil
}
// Categorise the file
switch {
case info.IsDir():
result.AddDir(dir)
case strings.HasSuffix(info.Name(), templateSuffix):
result.AddTemplate(dir)
default:
result.AddStandardFile(dir)
}
return nil
}) })
if err != nil {
return nil, fmt.Errorf("error processing template '%s' in path '%q': %v", templateName, templateDir, err)
}
return result, err
}
// Install the template files into the given project path
func (t *templateFiles) Install(projectPath string, projectOptions *ProjectOptions) error {
fs := NewFSHelper()
// Create directories
var targetDir string
for _, dirname := range t.Dirs {
targetDir = filepath.Join(projectPath, dirname)
fs.MkDir(targetDir)
}
// Copy standard files
var targetFile, sourceFile string
var err error var err error
for _, filename := range t.StandardFiles { templateFiles.Each(func(templateFile string) {
sourceFile = filepath.Join(t.BaseDir, filename)
targetFile = filepath.Join(projectPath, filename)
err = fs.CopyFile(sourceFile, targetFile) // Setup filenames
relativeFilename := strings.TrimPrefix(templateFile, templatePath)[1:]
targetFilename, err := filepath.Abs(filepath.Join(projectOptions.OutputDirectory, relativeFilename))
if err != nil { if err != nil {
return err return
}
} }
filedata := projectOptions.templates.Files.Bytes(templateFile)
// Do we have template files? // If file is a template, process it
if len(t.Templates) > 0 { if strings.HasSuffix(templateFile, ".template") {
templateData := projectOptions.templates.Files.String(templateFile)
// Iterate over the templates tmpl := template.New(templateFile)
var templateFile string tmpl.Parse(templateData)
var tmpl *template.Template
for _, filename := range t.Templates {
// Load template text
templateFile = filepath.Join(t.BaseDir, filename)
templateText, err := fs.LoadAsString(templateFile)
if err != nil {
return err
}
// Apply template
tmpl = template.New(templateFile)
tmpl.Parse(templateText)
// Write the template to a buffer
var tpl bytes.Buffer var tpl bytes.Buffer
err = tmpl.Execute(&tpl, projectOptions) err = tmpl.Execute(&tpl, projectOptions)
if err != nil { if err != nil {
fmt.Println("ERROR!!! " + err.Error()) return
return err
} }
// Save buffer to disk // Remove template suffix
targetFilename := strings.TrimSuffix(filename, templateSuffix) targetFilename = strings.TrimSuffix(targetFilename, ".template")
targetFile = filepath.Join(projectPath, targetFilename)
err = ioutil.WriteFile(targetFile, tpl.Bytes(), 0644) // Set the filedata to the template result
filedata = tpl.Bytes()
}
// Normal file, just copy it
err = fs.CreateFile(targetFilename, filedata)
if err != nil {
return
}
})
if err != nil { if err != nil {
return err return err
} }
}
}
return nil return nil
} }

View File

@ -17,7 +17,7 @@ Any flags that are required and not given will be prompted for.`
LongDescription(commandDescription). LongDescription(commandDescription).
BoolFlag("f", "Use defaults", &projectOptions.UseDefaults). BoolFlag("f", "Use defaults", &projectOptions.UseDefaults).
StringFlag("dir", "Directory to create project in", &projectOptions.OutputDirectory). StringFlag("dir", "Directory to create project in", &projectOptions.OutputDirectory).
StringFlag("template", "Template name", &projectOptions.Template). // StringFlag("template", "Template name", &projectOptions.Template).
StringFlag("name", "Project name", &projectOptions.Name). StringFlag("name", "Project name", &projectOptions.Name).
StringFlag("description", "Project description", &projectOptions.Description). StringFlag("description", "Project description", &projectOptions.Description).
StringFlag("output", "Output binary name", &projectOptions.BinaryName) StringFlag("output", "Output binary name", &projectOptions.BinaryName)

4
go.mod
View File

@ -27,8 +27,8 @@ require (
github.com/grpc-ecosystem/grpc-gateway v1.7.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.7.0 // indirect
github.com/jackmordaunt/icns v1.0.0 github.com/jackmordaunt/icns v1.0.0
github.com/kisielk/errcheck v1.2.0 // indirect github.com/kisielk/errcheck v1.2.0 // indirect
github.com/leaanthony/mewn v0.9.1 github.com/leaanthony/mewn v0.9.4
github.com/leaanthony/slicer v1.0.0 github.com/leaanthony/slicer v1.2.0
github.com/leaanthony/spinner v0.5.0 github.com/leaanthony/spinner v0.5.0
github.com/mattn/go-colorable v0.1.0 // indirect github.com/mattn/go-colorable v0.1.0 // indirect
github.com/microcosm-cc/bluemonday v1.0.2 // indirect github.com/microcosm-cc/bluemonday v1.0.2 // indirect

4
go.sum
View File

@ -101,8 +101,12 @@ github.com/leaanthony/mewn v0.9.0 h1:GExA7M15ikWytAIBKgs9SDl9EOC04JSjvL29qiLTAq4
github.com/leaanthony/mewn v0.9.0/go.mod h1:CRkTx8unLiSSilu/Sd7i1LwrdaAL+3eQ3ses99qGMEQ= github.com/leaanthony/mewn v0.9.0/go.mod h1:CRkTx8unLiSSilu/Sd7i1LwrdaAL+3eQ3ses99qGMEQ=
github.com/leaanthony/mewn v0.9.1 h1:+qmAnR5nETU/00o5wvYJ7w9Y36R9uIBRB9I15E9Ru8A= github.com/leaanthony/mewn v0.9.1 h1:+qmAnR5nETU/00o5wvYJ7w9Y36R9uIBRB9I15E9Ru8A=
github.com/leaanthony/mewn v0.9.1/go.mod h1:CRkTx8unLiSSilu/Sd7i1LwrdaAL+3eQ3ses99qGMEQ= github.com/leaanthony/mewn v0.9.1/go.mod h1:CRkTx8unLiSSilu/Sd7i1LwrdaAL+3eQ3ses99qGMEQ=
github.com/leaanthony/mewn v0.9.4 h1:thDAdV8DkCwqHcFLnfMLQtTHqK1N8leF7sD/SPsLMHI=
github.com/leaanthony/mewn v0.9.4/go.mod h1:CRkTx8unLiSSilu/Sd7i1LwrdaAL+3eQ3ses99qGMEQ=
github.com/leaanthony/slicer v1.0.0 h1:BV2CySexcZ20qyHp5qBTxQhsazR6e8MBTF1EHmGu1xw= github.com/leaanthony/slicer v1.0.0 h1:BV2CySexcZ20qyHp5qBTxQhsazR6e8MBTF1EHmGu1xw=
github.com/leaanthony/slicer v1.0.0/go.mod h1:VMB/HGvr3uR3MRpFWHWAm0w+DHQLzPHYe2pKfpFlQIQ= github.com/leaanthony/slicer v1.0.0/go.mod h1:VMB/HGvr3uR3MRpFWHWAm0w+DHQLzPHYe2pKfpFlQIQ=
github.com/leaanthony/slicer v1.2.0 h1:XZ+1l7cCO36j238iv5ZXkJAcM3hPtD0xb41/WE+QmuU=
github.com/leaanthony/slicer v1.2.0/go.mod h1:VMB/HGvr3uR3MRpFWHWAm0w+DHQLzPHYe2pKfpFlQIQ=
github.com/leaanthony/spinner v0.5.0 h1:HQykt/iTy7fmINEREtRbWrt+8j4MxC8dtvWBxEWM9oA= github.com/leaanthony/spinner v0.5.0 h1:HQykt/iTy7fmINEREtRbWrt+8j4MxC8dtvWBxEWM9oA=
github.com/leaanthony/spinner v0.5.0/go.mod h1:8TSFz9SL1AUC4XSbEFYE6SfN5Mlus51qYluVGrie9ww= github.com/leaanthony/spinner v0.5.0/go.mod h1:8TSFz9SL1AUC4XSbEFYE6SfN5Mlus51qYluVGrie9ww=
github.com/leaanthony/synx v0.1.0 h1:R0lmg2w6VMb8XcotOwAe5DLyzwjLrskNkwU7LLWsyL8= github.com/leaanthony/synx v0.1.0 h1:R0lmg2w6VMb8XcotOwAe5DLyzwjLrskNkwU7LLWsyL8=

File diff suppressed because one or more lines are too long