1package config
2
3import (
4 "fmt"
5 "os"
6 "path/filepath"
7 "slices"
8 "strings"
9
10 "github.com/charmbracelet/crush/internal/fsext"
11)
12
13const (
14 InitFlagFilename = "init"
15)
16
17type ProjectInitFlag struct {
18 Initialized bool `json:"initialized"`
19}
20
21func Init(workingDir, dataDir string, debug bool) (*Config, error) {
22 cfg, err := Load(workingDir, dataDir, debug)
23 if err != nil {
24 return nil, err
25 }
26 return cfg, nil
27}
28
29func ProjectNeedsInitialization(cfg *Config) (bool, error) {
30 if cfg == nil {
31 return false, fmt.Errorf("config not loaded")
32 }
33
34 flagFilePath := filepath.Join(cfg.Options.DataDirectory, InitFlagFilename)
35
36 _, err := os.Stat(flagFilePath)
37 if err == nil {
38 return false, nil
39 }
40
41 if !os.IsNotExist(err) {
42 return false, fmt.Errorf("failed to check init flag file: %w", err)
43 }
44
45 someContextFileExists, err := contextPathsExist(cfg.WorkingDir())
46 if err != nil {
47 return false, fmt.Errorf("failed to check for context files: %w", err)
48 }
49 if someContextFileExists {
50 return false, nil
51 }
52
53 // If the working directory has no non-ignored files, skip initialization step
54 empty, err := dirHasNoVisibleFiles(cfg.WorkingDir())
55 if err != nil {
56 return false, fmt.Errorf("failed to check if directory is empty: %w", err)
57 }
58 if empty {
59 return false, nil
60 }
61
62 return true, nil
63}
64
65func contextPathsExist(dir string) (bool, error) {
66 entries, err := os.ReadDir(dir)
67 if err != nil {
68 return false, err
69 }
70
71 // Create a slice of lowercase filenames for lookup with slices.Contains
72 var files []string
73 for _, entry := range entries {
74 if !entry.IsDir() {
75 files = append(files, strings.ToLower(entry.Name()))
76 }
77 }
78
79 // Check if any of the default context paths exist in the directory
80 for _, path := range defaultContextPaths {
81 // Extract just the filename from the path
82 _, filename := filepath.Split(path)
83 filename = strings.ToLower(filename)
84
85 if slices.Contains(files, filename) {
86 return true, nil
87 }
88 }
89
90 return false, nil
91}
92
93// dirHasNoVisibleFiles returns true if the directory has no files/dirs after applying ignore rules
94func dirHasNoVisibleFiles(dir string) (bool, error) {
95 files, _, err := fsext.ListDirectory(dir, nil, 1, 1)
96 if err != nil {
97 return false, err
98 }
99 return len(files) == 0, nil
100}
101
102func MarkProjectInitialized(cfg *Config) error {
103 if cfg == nil {
104 return fmt.Errorf("config not loaded")
105 }
106 flagFilePath := filepath.Join(cfg.Options.DataDirectory, InitFlagFilename)
107
108 file, err := os.Create(flagFilePath)
109 if err != nil {
110 return fmt.Errorf("failed to create init flag file: %w", err)
111 }
112 defer file.Close()
113
114 return nil
115}
116
117func HasInitialDataConfig(cfg *Config) bool {
118 if cfg == nil {
119 return false
120 }
121 cfgPath := GlobalConfigData()
122 if _, err := os.Stat(cfgPath); err != nil {
123 return false
124 }
125 return cfg.IsConfigured()
126}