Commit 10e727f
Changed files (10)
internal
home/.bashrc.d/go.sh
@@ -1,3 +0,0 @@
-#!/bin/bash
-
-export PATH="${PATH}:/usr/local/go/bin"
home/.bashrc.d/zig.sh
@@ -1,3 +0,0 @@
-#!/bin/bash
-
-export PATH="${PATH}:${HOME}/.local/zig"
internal/bashrcd/bashrc.go
@@ -0,0 +1,88 @@
+package bashrcd
+
+import (
+ _ "embed"
+ "bufio"
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+//go:embed bashrc.part
+var bashrcPart string
+
+const (
+ startMarker = "# BEGIN BINDLE MANAGED SECTION"
+ endMarker = "# END BINDLE MANAGED SECTION"
+)
+
+// Install ensures the bashrc.part content is properly integrated into ~/.bashrc
+// without duplication, using markers to manage the section
+func Install() error {
+ homeDir := os.Getenv("HOME")
+ bashrcPath := filepath.Join(homeDir, ".bashrc")
+
+ // Read existing bashrc or create empty content
+ var lines []string
+ if _, err := os.Stat(bashrcPath); err == nil {
+ file, err := os.Open(bashrcPath)
+ if err != nil {
+ return fmt.Errorf("opening bashrc: %w", err)
+ }
+ defer file.Close()
+
+ scanner := bufio.NewScanner(file)
+ for scanner.Scan() {
+ lines = append(lines, scanner.Text())
+ }
+ if err := scanner.Err(); err != nil {
+ return fmt.Errorf("reading bashrc: %w", err)
+ }
+ }
+
+ // Find and remove existing bindle section
+ var newLines []string
+ inBindleSection := false
+
+ for _, line := range lines {
+ if strings.Contains(line, startMarker) {
+ inBindleSection = true
+ continue
+ }
+ if strings.Contains(line, endMarker) {
+ inBindleSection = false
+ continue
+ }
+ if !inBindleSection {
+ newLines = append(newLines, line)
+ }
+ }
+
+ // Add bindle section at the end
+ newLines = append(newLines, "")
+ newLines = append(newLines, startMarker)
+
+ // Add each line from bashrc.part
+ partLines := strings.Split(strings.TrimSpace(bashrcPart), "\n")
+ for _, line := range partLines {
+ newLines = append(newLines, line)
+ }
+
+ newLines = append(newLines, endMarker)
+
+ // Write back to bashrc
+ file, err := os.Create(bashrcPath)
+ if err != nil {
+ return fmt.Errorf("creating bashrc: %w", err)
+ }
+ defer file.Close()
+
+ for _, line := range newLines {
+ if _, err := fmt.Fprintln(file, line); err != nil {
+ return fmt.Errorf("writing bashrc: %w", err)
+ }
+ }
+
+ return nil
+}
\ No newline at end of file
internal/bashrcd/bashrc.part
@@ -0,0 +1,8 @@
+# source all .sh files in bashrc.d
+if [ -d "${HOME}/.bashrc.d" ]; then
+ for rc in "${HOME}/.bashrc.d"/*.sh; do
+ if [ -f "$rc" ]; then
+ source "$rc"
+ fi
+ done
+fi
internal/extractor/extractor.go
@@ -9,8 +9,12 @@ import (
"os"
"path/filepath"
"slices"
+ "time"
"github.com/aymanbagabas/go-udiff"
+ "github.com/charmbracelet/bubbles/progress"
+ tea "github.com/charmbracelet/bubbletea"
+ "github.com/charmbracelet/lipgloss"
)
var (
@@ -41,6 +45,105 @@ type Extractor struct {
Results []ExtractResult // per-file results
}
+type progressModel struct {
+ progress progress.Model
+ current int
+ total int
+ done bool
+ spinner int
+}
+
+var spinnerFrames = []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}
+
+func (m progressModel) Init() tea.Cmd {
+ return tea.Tick(time.Millisecond*100, func(time.Time) tea.Msg {
+ return tickMsg{}
+ })
+}
+
+func (m progressModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
+ switch msg := msg.(type) {
+ case tea.KeyMsg:
+ return m, tea.Quit
+ case tickMsg:
+ if !m.done {
+ m.spinner = (m.spinner + 1) % len(spinnerFrames)
+ return m, tea.Tick(time.Millisecond*150, func(time.Time) tea.Msg {
+ return tickMsg{}
+ })
+ }
+ return m, nil
+ case progressMsg:
+ m.current = msg.current
+ m.total = msg.total
+ if m.current >= m.total {
+ m.done = true
+ return m, tea.Quit
+ }
+ return m, nil
+ }
+ return m, nil
+}
+
+func (m progressModel) View() string {
+ if m.done {
+ // Completion style with home theme (purple/gray)
+ checkmark := lipgloss.NewStyle().
+ Foreground(lipgloss.Color("#9945ff")).
+ Bold(true).
+ Render("✓")
+
+ title := lipgloss.NewStyle().
+ Foreground(lipgloss.Color("#ffffff")).
+ Bold(true).
+ Render("Home Directory Setup Complete")
+
+ stats := lipgloss.NewStyle().
+ Foreground(lipgloss.Color("#666666")).
+ Render(fmt.Sprintf("(%d files processed)", m.total))
+
+ return fmt.Sprintf("\n %s %s %s\n\n", checkmark, title, stats)
+ }
+
+ // Progress bar styling with home-inspired colors (purple theme)
+ percentage := float64(m.current) / float64(m.total)
+
+ // Styled spinner
+ spinner := lipgloss.NewStyle().
+ Foreground(lipgloss.Color("#9945ff")).
+ Bold(true).
+ Render(spinnerFrames[m.spinner])
+
+ // Styled title
+ title := lipgloss.NewStyle().
+ Foreground(lipgloss.Color("#ffffff")).
+ Bold(true).
+ Render("Extracting Home Directory")
+
+ // Progress bar with custom styling
+ progressBar := m.progress.ViewAs(percentage)
+
+ // Styled stats
+ stats := lipgloss.NewStyle().
+ Foreground(lipgloss.Color("#888888")).
+ Render(fmt.Sprintf("%d/%d files (%.1f%%)", m.current, m.total, percentage*100))
+
+ // Status indicator
+ statusIndicator := lipgloss.NewStyle().
+ Foreground(lipgloss.Color("#bb77ff")).
+ Render(" • processing...")
+
+ return fmt.Sprintf("\n %s %s\n %s\n %s%s\n",
+ spinner, title, progressBar, stats, statusIndicator)
+}
+
+type progressMsg struct {
+ current int
+ total int
+}
+
+type tickMsg struct{}
+
// TODO: Options
func New(
fs fs.FS,
@@ -56,7 +159,79 @@ func New(
}
func (e *Extractor) Deploy() error {
- return fs.WalkDir(e.fs, ".", e.deployFile)
+ return e.DeployWithProgress()
+}
+
+func (e *Extractor) DeployWithProgress() error {
+ // Count total files first
+ totalFiles, err := e.countFiles()
+ if err != nil {
+ return fmt.Errorf("counting files: %w", err)
+ }
+
+ if totalFiles == 0 {
+ return nil // Nothing to extract
+ }
+
+ // Setup enhanced progress bar with home-inspired colors (purple theme)
+ p := progress.New(
+ progress.WithScaledGradient("#9945ff", "#bb77ff"),
+ progress.WithWidth(50),
+ progress.WithoutPercentage(),
+ )
+
+ model := progressModel{
+ progress: p,
+ total: totalFiles,
+ }
+
+ program := tea.NewProgram(model)
+ var deployErr error
+
+ go func() {
+ fileCount := 0
+ walkErr := fs.WalkDir(e.fs, ".", func(path string, d fs.DirEntry, err error) error {
+ fileCount++
+
+ // Send progress update
+ program.Send(progressMsg{current: fileCount, total: totalFiles})
+
+ // Call the original deploy logic
+ return e.deployFile(path, d, err)
+ })
+
+ if walkErr != nil {
+ deployErr = walkErr
+ program.Quit()
+ return
+ }
+
+ // Signal completion
+ program.Send(progressMsg{current: totalFiles, total: totalFiles})
+ }()
+
+ _, err = program.Run()
+ if err != nil {
+ return fmt.Errorf("running progress: %w", err)
+ }
+
+ if deployErr != nil {
+ return deployErr
+ }
+
+ return nil
+}
+
+func (e *Extractor) countFiles() (int, error) {
+ count := 0
+ err := fs.WalkDir(e.fs, ".", func(path string, d fs.DirEntry, err error) error {
+ if err != nil {
+ return err
+ }
+ count++
+ return nil
+ })
+ return count, err
}
// Deploy extracts an the embedded extractor.fs filesystem into the exractor.dst
internal/golang/golang.go
@@ -66,7 +66,7 @@ func (m progressModel) View() string {
if m.done {
// Completion style
checkmark := lipgloss.NewStyle().
- Foreground(lipgloss.Color("#00ff00")).
+ Foreground(lipgloss.Color("#00add8")).
Bold(true).
Render("✓")
@@ -87,7 +87,7 @@ func (m progressModel) View() string {
// Styled spinner
spinner := lipgloss.NewStyle().
- Foreground(lipgloss.Color("#00ffff")).
+ Foreground(lipgloss.Color("#00add8")).
Bold(true).
Render(spinnerFrames[m.spinner])
@@ -109,7 +109,7 @@ func (m progressModel) View() string {
rate := ""
if m.current > 100 {
rate = lipgloss.NewStyle().
- Foreground(lipgloss.Color("#ffff00")).
+ Foreground(lipgloss.Color("#00add8")).
Render(" • extracting...")
}
@@ -172,7 +172,7 @@ func Install() error {
// Setup enhanced progress bar with custom styling
p := progress.New(
- progress.WithScaledGradient("#ff7700", "#ffdd00"),
+ progress.WithScaledGradient("#ffffff", "#00add8"),
progress.WithWidth(50),
progress.WithoutPercentage(),
)
internal/nvim/nvim.go
@@ -152,6 +152,7 @@ func bashrcd() error {
const tmpl = `#!/bin/bash
export PATH=$PATH:{{ .InstallDir }}/bin
+alias vim=nvim
`
t, err := template.New("pathRC").Parse(tmpl)
internal/zig/zig.go
@@ -73,7 +73,7 @@ func (m progressModel) View() string {
checkmark := lipgloss.NewStyle().
Foreground(lipgloss.Color("#f7a41d")).
Bold(true).
- Render("⚡")
+ Render("✓")
title := lipgloss.NewStyle().
Foreground(lipgloss.Color("#ffffff")).
main.go
@@ -8,6 +8,7 @@ import (
"log/slog"
"os"
+ "github.com/bryfry/bindle/internal/bashrcd"
"github.com/bryfry/bindle/internal/extractor"
"github.com/bryfry/bindle/internal/golang"
"github.com/bryfry/bindle/internal/nvim"
@@ -39,6 +40,12 @@ func main() {
return // fmt.Errorf("config deploy failed: %w", path, err)
}
+ err = bashrcd.Install()
+ if err != nil {
+ fmt.Println(err)
+ return // fmt.Errorf("bashrc integration failed: %w", err)
+ }
+
contentFS, err := fs.Sub(_homeFS, _homeRoot)
if err != nil {
fmt.Println(err)
README.md
@@ -7,7 +7,7 @@
- [x] golang
- [x] nvim
- [x] nvim.kickstart
-- [ ] zig
+- [x] zig
- [ ] bashrc.d
- [ ] apt
- vim, tmux, htop, curl, shellcheck, git, git-lfs