Files
dirmd/internal/tui/run.go
T
2026-08-27 20:09:59 +01:00

205 lines
5.0 KiB
Go

package tui
import (
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"charm.land/huh/v2"
"charm.land/huh/v2/spinner"
"dirmd/internal/config"
)
func Run() error {
var (
inputDir string
outputDir string
force bool
verticalSlices bool
skipFrontend bool
frontendOnly bool
omitTest bool
mitMd bool
protonDrive bool
drivePath string
maxDepthStr string
maxSizeStr string
ignorePatterns string
)
inputDir = "."
maxDepthStr = "20"
maxSizeStr = "512"
defOut := defaultOutputDir()
outputDir = defOut
form := huh.NewForm(
huh.NewGroup(
huh.NewInput().Title("Input directory").Value(&inputDir).Placeholder("."),
huh.NewInput().Title("Output directory").Value(&outputDir).Placeholder(defOut),
huh.NewConfirm().Title("Force overwrite?").Value(&force),
huh.NewConfirm().Title("Vertical slices?").Value(&verticalSlices),
huh.NewConfirm().Title("Skip frontend files?").Value(&skipFrontend),
huh.NewConfirm().Title("Frontend only?").Value(&frontendOnly),
huh.NewConfirm().Title("Omit test files?").Value(&omitTest),
huh.NewConfirm().Title("Omit non-README markdown?").Value(&mitMd),
huh.NewConfirm().Title("Upload to Proton Drive?").Value(&protonDrive),
huh.NewInput().Title("Max depth").Value(&maxDepthStr).Placeholder("20"),
huh.NewInput().Title("Max size (KB)").Value(&maxSizeStr).Placeholder("512"),
huh.NewText().Title("Ignore patterns (one per line)").Value(&ignorePatterns).Placeholder("One pattern per line..."),
),
huh.NewGroup(
huh.NewInput().Title("Remote path on Proton Drive").Value(&drivePath).Placeholder("/my-files/md/"),
).WithHideFunc(func() bool {
return !protonDrive
}),
)
if err := form.Run(); err != nil {
return fmt.Errorf("form: %w", err)
}
maxDepth, err := strconv.Atoi(strings.TrimSpace(maxDepthStr))
if err != nil || maxDepth <= 0 {
maxDepth = 20
}
maxSizeKB, err := strconv.Atoi(strings.TrimSpace(maxSizeStr))
if err != nil || maxSizeKB <= 0 {
maxSizeKB = 512
}
var ignores []string
raw := strings.TrimSpace(ignorePatterns)
if raw != "" {
for _, line := range strings.Split(raw, "\n") {
line = strings.TrimSpace(line)
if line != "" {
ignores = append(ignores, line)
}
}
}
if skipFrontend && frontendOnly {
return fmt.Errorf("--skip-frontend and --frontend-only are mutually exclusive")
}
excludedExts := append([]string{}, config.DefaultExts...)
if skipFrontend {
excludedExts = append(excludedExts, config.FrontendExts...)
}
allIgnores := append([]string{}, config.DefaultIgnores...)
allIgnores = append(allIgnores, ignores...)
cfg := &config.Config{
InputPath: inputDir,
OutputPath: outputDir,
Force: force,
MaxSize: int64(maxSizeKB) * 1024,
MaxDepth: maxDepth,
Ignores: allIgnores,
Extensions: excludedExts,
ProtonDrive: protonDrive,
DrivePath: drivePath,
VerticalSlices: verticalSlices,
SkipFrontend: skipFrontend,
FrontendOnly: frontendOnly,
OmitTest: omitTest,
OmitMd: mitMd,
}
cmd := buildCommand(cfg)
fmt.Fprintf(os.Stderr, "%s\n", cmd)
var result GenerateResult
if err := spinner.New().
Title("Generating...").
Action(func() {
result = Generate(cfg)
}).
Run(); err != nil {
return fmt.Errorf("interrupted: %w", err)
}
if result.Error != nil {
return result.Error
}
for _, f := range result.Files {
fmt.Fprintf(os.Stdout, " %s\n", f)
}
if result.Uploaded {
fmt.Fprintln(os.Stdout, "Uploaded to Proton Drive")
}
return nil
}
func defaultOutputDir() string {
cwd, err := os.Getwd()
if err != nil {
return "/tmp/output_dirmd"
}
base := filepath.Base(cwd)
return filepath.Join("/tmp", base+"_dirmd")
}
func buildCommand(cfg *config.Config) string {
var parts []string
parts = append(parts, "dirmd")
parts = append(parts, "-o", cfg.OutputPath)
if cfg.Force {
parts = append(parts, "-f")
}
if cfg.VerticalSlices {
parts = append(parts, "--vertical-slices")
}
if cfg.SkipFrontend {
parts = append(parts, "--skip-frontend")
}
if cfg.FrontendOnly {
parts = append(parts, "--frontend-only")
}
if cfg.OmitTest {
parts = append(parts, "--omit-test")
}
if cfg.OmitMd {
parts = append(parts, "--omit-md")
}
if cfg.ProtonDrive {
parts = append(parts, "--proton-drive")
if cfg.DrivePath != "" {
parts = append(parts, "--drive-path", cfg.DrivePath)
}
}
if cfg.MaxDepth != 20 {
parts = append(parts, "--max-depth", strconv.Itoa(cfg.MaxDepth))
}
if cfg.MaxSize != 512*1024 {
parts = append(parts, "--max-size", strconv.FormatInt(cfg.MaxSize, 10))
}
for _, ig := range extraIgnores(cfg) {
parts = append(parts, "--ignore", ig)
}
parts = append(parts, cfg.InputPath)
return strings.Join(parts, " ")
}
func extraIgnores(cfg *config.Config) []string {
defaultSet := make(map[string]bool)
for _, d := range config.DefaultIgnores {
defaultSet[d] = true
}
var extra []string
for _, ig := range cfg.Ignores {
if !defaultSet[ig] {
extra = append(extra, ig)
}
}
return extra
}