Commit 10e727f

bryfry <bryon@fryer.io>
2025-08-09 15:52:40
bashrcd, colors in progress
1 parent fc8cb54
Changed files (10)
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