feat(vendordep): add vendordep management

- Add vendordep add
- Add vendordep remove
- Add vendordep list
- Refactor downloading
This commit is contained in:
2025-10-17 14:15:37 -04:00
parent e073c0d391
commit 2a22f41fac
18 changed files with 1099 additions and 184 deletions

View File

@@ -1,76 +0,0 @@
package template
import (
"strings"
"time"
"github.com/charmbracelet/bubbles/progress"
tea "github.com/charmbracelet/bubbletea"
)
const (
padding = 2
maxWidth = 80
)
type progressMsg float64
type progressErrMsg struct{ err error }
func finalPause() tea.Cmd {
return tea.Tick(time.Millisecond*750, func(_ time.Time) tea.Msg {
return nil
})
}
type downloadModel struct {
pw *progressWriter
progress progress.Model
err error
}
func (m downloadModel) Init() tea.Cmd {
return nil
}
func (m downloadModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
return m, tea.Quit
case tea.WindowSizeMsg:
m.progress.Width = min(msg.Width - padding * 2 - 4, maxWidth)
return m, nil
case progressErrMsg:
m.err = msg.err
return m, tea.Quit
case progressMsg:
var cmds []tea.Cmd
if msg >= 1.0 {
cmds = append(cmds, tea.Sequence(finalPause(), tea.Quit))
}
cmds = append(cmds, m.progress.SetPercent(float64(msg)))
return m, tea.Batch(cmds...)
// FrameMsg is sent when the progress bar wants to animate itself
case progress.FrameMsg:
progressModel, cmd := m.progress.Update(msg)
m.progress = progressModel.(progress.Model)
return m, cmd
default:
return m, nil
}
}
func (m downloadModel) View() string {
if m.err != nil {
return "Error downloading: " + m.err.Error() + "\n"
}
pad := strings.Repeat(" ", padding)
return "\n" + pad + "Downloading template.zip to " + m.pw.file.Name() + "\n\n" + pad + m.progress.View() + "\n\n" + pad + "Press any key to quit"
}

View File

@@ -10,32 +10,9 @@ import (
"strconv"
"rph/state"
"rph/utils"
)
type progressWriter struct {
total int
downloaded int
file *os.File
reader io.Reader
onProgress func(float64)
}
func (pw *progressWriter) Start() {
// TeeReader calls pw.Write() each time a new response is received
_, err := io.Copy(pw.file, io.TeeReader(pw.reader, pw))
if err != nil {
slog.Error("Error in progress writer", "error", progressErrMsg{err})
}
}
func (pw *progressWriter) Write(p []byte) (int, error) {
pw.downloaded += len(p)
if pw.total > 0 && pw.onProgress != nil {
pw.onProgress(float64(pw.downloaded) / float64(pw.total))
}
return len(p), nil
}
type asset struct {
Name string `json:"name"`
BrowserDownloadURL string `json:"browser_download_url"`
@@ -104,7 +81,7 @@ func getTemplateArchive(filename string, force bool, version string) {
return
}
err = downloadFile(downloadURL)
err = utils.DownloadFile(downloadURL, filepath.Join(state.CachePath, zipFile))
if err != nil {
slog.Error("Error downloading archive file", "error", err)
os.Exit(1)

View File

@@ -5,13 +5,10 @@ import (
"errors"
"io/fs"
"log/slog"
"net/http"
"os"
"path/filepath"
"rph/state"
"github.com/charmbracelet/bubbles/progress"
tea "github.com/charmbracelet/bubbletea"
"github.com/mholt/archives"
)
@@ -34,57 +31,6 @@ func LoadArchiveVersion() (string, error) {
return string(data), nil
}
func downloadFile(url string) error {
var progressBar = true
// Create the file
out, err := os.Create(filepath.Join(state.CachePath, zipFile))
if err != nil {
return err
}
defer out.Close()
// Get the data
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.ContentLength <= 0 {
slog.Warn("Can't parse content length, no progress bar will be shown.")
progressBar = false
}
var p *tea.Program
pw := &progressWriter{
total: int(resp.ContentLength),
file: out,
reader: resp.Body,
onProgress: func(ratio float64) {
p.Send(progressMsg(ratio))
},
}
m := downloadModel{
pw: pw,
progress: progress.New(progress.WithDefaultGradient()),
}
p = tea.NewProgram(m)
// start the download
go pw.Start()
if progressBar {
if _, err := p.Run(); err != nil {
slog.Error("Error starting the progress bar", "error", err)
}
}
return err
}
func OpenArchive(ctx context.Context) (fsys fs.FS, err error) {
fsys, err = archives.FileSystem(ctx, filepath.Join(state.CachePath, zipFile), nil)
if err != nil {

View File

@@ -3,12 +3,15 @@ package template
import (
"context"
"encoding/json"
"fmt"
"io/fs"
"log/slog"
"os"
"path/filepath"
"regexp"
"rph/state"
"rph/utils"
"strings"
)
type TemplateOptions struct {
@@ -19,7 +22,7 @@ type TemplateOptions struct {
DesktopSupport *bool
}
type wpilibPreferences struct {
type WpilibPreferences struct {
CppIntellisense bool `json:"enableCppIntellisense"`
Lang string `json:"currentLanguage"`
Year string `json:"projectYear"`
@@ -85,7 +88,7 @@ func GenerateProject(opts TemplateOptions) {
defer file.Close()
{ // scope it so I can jump over it
var p wpilibPreferences
var p WpilibPreferences
decoder := json.NewDecoder(file)
err = decoder.Decode(&p)
if err != nil {
@@ -188,14 +191,14 @@ func GenerateProject(opts TemplateOptions) {
if strings.HasPrefix(projType, "xrp") {
url := fmt.Sprintf("https://raw.githubusercontent.com/wpilibsuite/allwpilib/%s/xrpVendordep/XRPVendordep.json", version)
err = fetchWPIDep(url, filepath.Join(vendordepDir, "XRPVendordep.json"))
err = utils.DownloadFile(url, filepath.Join(vendordepDir, "XRPVendordep.json"))
if err != nil {
slog.Error("Failed to download required vendor dep you should download it yourself", "url", url, "error", err)
return
}
} else if strings.HasPrefix(projType, "romi") {
url := fmt.Sprintf("https://raw.githubusercontent.com/wpilibsuite/allwpilib/%s/romiVendordep/RomiVendordep.json", version)
err = fetchWPIDep(url, filepath.Join(vendordepDir, "RomiVendordep.json"))
err = utils.DownloadFile(url, filepath.Join(vendordepDir, "RomiVendordep.json"))
if err != nil {
slog.Error("Failed to download required vendor dep you should download it yourself", "url", url, "error", err)
return
@@ -203,7 +206,7 @@ func GenerateProject(opts TemplateOptions) {
} else if strings.HasPrefix(projType, "command") {
// TODO: make sure there's no other project types this has to work with
url := fmt.Sprintf("https://raw.githubusercontent.com/wpilibsuite/allwpilib/%s/wpilibNewCommands/WPILibNewCommands.json", version)
err = fetchWPIDep(url, filepath.Join(vendordepDir, "WPILibNewCommands.json"))
err = utils.DownloadFile(url, filepath.Join(vendordepDir, "WPILibNewCommands.json"))
if err != nil {
slog.Error("Failed to download required vendor dep you should download it yourself", "url", url, "error", err)
return
@@ -211,27 +214,3 @@ func GenerateProject(opts TemplateOptions) {
}
}
}
func fetchWPIDep(url string, outpath string) error {
resp, err := http.Get(url)
if err != nil {
slog.Error("Failed to download file", "url", url, "error", err)
return err
}
defer resp.Body.Close()
outFile, err := os.Create(outpath)
if err != nil {
slog.Error("Failed to create file", "error", err)
return err
}
defer outFile.Close()
_, err = io.Copy(outFile, resp.Body)
if err != nil {
slog.Error("Failed to save file", "error", err)
return err
}
return nil
}