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