@@ -24,7 +24,6 @@ import (
2424 "io/fs"
2525 "os"
2626 "path/filepath"
27- "regexp"
2827 "slices"
2928 "strings"
3029)
@@ -33,6 +32,12 @@ type Config struct {
3332 // Filename to look for the root of a package.
3433 PackageFile []string `json:"package-file"`
3534
35+ // CI setup file, must be located in the same directory as the package file.
36+ CISetupFileName string `json:"ci-setup-filename"`
37+
38+ // CI setup defaults, used when no setup file or field is not sepcified in file.
39+ CISetupDefaults CISetup `json:"ci-setup-defaults"`
40+
3641 // Pattern to match filenames or directories.
3742 Match []string `json:"match"`
3843
@@ -43,8 +48,7 @@ type Config struct {
4348 ExcludePackages []string `json:"exclude-packages"`
4449}
4550
46- var multiLineCommentsRegex = regexp .MustCompile (`(?s)\s*/\*.*?\*/` )
47- var singleLineCommentsRegex = regexp .MustCompile (`\s*//.*\s*` )
51+ type CISetup = map [string ]any
4852
4953// Saves the config to the given file.
5054func (c * Config ) Save (file * os.File ) error {
@@ -61,30 +65,22 @@ func (c *Config) Save(file *os.File) error {
6165
6266// LoadConfig loads the config from the given path.
6367func LoadConfig (path string ) (* Config , error ) {
64- // Read the JSONC file.
65- sourceJsonc , err := os .ReadFile (path )
66- if err != nil {
67- return nil , err
68+ // Set the config default values.
69+ config := Config {
70+ Match : []string {"*" },
6871 }
6972
70- // Strip the comments and load the JSON.
71- sourceJson := multiLineCommentsRegex .ReplaceAll (sourceJsonc , []byte {})
72- sourceJson = singleLineCommentsRegex .ReplaceAll (sourceJson , []byte {})
73-
74- var config Config
75- err = json .Unmarshal (sourceJson , & config )
73+ // This mutates `config` so there's no need to reassign it.
74+ // It keeps the default values if they're not in the JSON file.
75+ err := readJsonc (path , & config )
7676 if err != nil {
7777 return nil , err
7878 }
7979
80- // Set default values if they are not set .
80+ // Validate for required values .
8181 if config .PackageFile == nil {
8282 return nil , errors .New ("package-file is required" )
8383 }
84- if config .Match == nil {
85- config .Match = []string {"*" }
86- }
87-
8884 return & config , nil
8985}
9086
@@ -110,15 +106,14 @@ func (c *Config) Matches(path string) bool {
110106// IsPackageDir returns true if the path is a package directory.
111107func (c * Config ) IsPackageDir (dir string ) bool {
112108 for _ , filename := range c .PackageFile {
113- packageFile := filepath .Join (dir , filename )
114- if fileExists (packageFile ) {
109+ if fileExists (filepath .Join (dir , filename )) {
115110 return true
116111 }
117112 }
118113 return false
119114}
120115
121- // FindPackage returns the package name for the given path .
116+ // FindPackage returns the most specific package path for the given filename .
122117func (c * Config ) FindPackage (path string ) string {
123118 dir := filepath .Dir (path )
124119 if dir == "." || c .IsPackageDir (dir ) {
@@ -127,9 +122,9 @@ func (c *Config) FindPackage(path string) string {
127122 return c .FindPackage (dir )
128123}
129124
130- // FindAllPackages finds all the packages in the given root directory.
125+ // FindAllPackages finds all the package paths in the given root directory.
131126func (c * Config ) FindAllPackages (root string ) ([]string , error ) {
132- var packages []string
127+ var paths []string
133128 err := fs .WalkDir (os .DirFS (root ), "." ,
134129 func (path string , d os.DirEntry , err error ) error {
135130 if err != nil {
@@ -142,26 +137,15 @@ func (c *Config) FindAllPackages(root string) ([]string, error) {
142137 return nil
143138 }
144139 if d .IsDir () && c .Matches (path ) && c .IsPackageDir (path ) {
145- packages = append (packages , path )
140+ paths = append (paths , path )
146141 return nil
147142 }
148143 return nil
149144 })
150145 if err != nil {
151146 return []string {}, err
152147 }
153- return packages , nil
154- }
155-
156- // Affected returns the packages that have been affected from diffs.
157- // If there are diffs on at leat one global file affecting all packages,
158- // then this returns all packages matched by the config.
159- func (c * Config ) Affected (log io.Writer , diffs []string ) ([]string , error ) {
160- changed := c .Changed (log , diffs )
161- if slices .Contains (changed , "." ) {
162- return c .FindAllPackages ("." )
163- }
164- return changed , nil
148+ return paths , nil
165149}
166150
167151// Changed returns the packages that have changed.
@@ -173,17 +157,57 @@ func (c *Config) Changed(log io.Writer, diffs []string) []string {
173157 if ! c .Matches (diff ) {
174158 continue
175159 }
176- pkg := c .FindPackage (diff )
177- changedUnique [pkg ] = true
160+ path := c .FindPackage (diff )
161+ if path == "." {
162+ fmt .Fprintf (log , "ℹ️ Global file changed: %q\n " , diff )
163+ }
164+ changedUnique [path ] = true
178165 }
179166
180167 changed := make ([]string , 0 , len (changedUnique ))
181- for pkg := range changedUnique {
182- if slices .Contains (c .ExcludePackages , pkg ) {
183- fmt .Fprintf (log , "ℹ️ Excluded package %q, skipping.\n " , pkg )
168+ for path := range changedUnique {
169+ if slices .Contains (c .ExcludePackages , path ) {
170+ fmt .Fprintf (log , "ℹ️ Excluded package %q, skipping.\n " , path )
184171 continue
185172 }
186- changed = append (changed , pkg )
173+ changed = append (changed , path )
187174 }
188175 return changed
189176}
177+
178+ // Affected returns the packages that have been affected from diffs.
179+ // If there are diffs on at leat one global file affecting all packages,
180+ // then this returns all packages matched by the config.
181+ func (c * Config ) Affected (log io.Writer , diffs []string ) ([]string , error ) {
182+ paths := c .Changed (log , diffs )
183+ if slices .Contains (paths , "." ) {
184+ fmt .Fprintf (log , "One or more global files were affected, all packages marked as affected.\n " )
185+ allPackages , err := c .FindAllPackages ("." )
186+ if err != nil {
187+ return nil , err
188+ }
189+ paths = allPackages
190+ }
191+ return paths , nil
192+ }
193+
194+ func (c * Config ) FindSetupFiles (paths []string ) (* map [string ]CISetup , error ) {
195+ setups := make (map [string ]CISetup , len (paths ))
196+ for _ , path := range paths {
197+ setup := make (CISetup , len (c .CISetupDefaults ))
198+ for k , v := range c .CISetupDefaults {
199+ setup [k ] = v
200+ }
201+ setupFile := filepath .Join (path , c .CISetupFileName )
202+ if c .CISetupFileName != "" && fileExists (setupFile ) {
203+ // This mutates `setup` so there's no need to reassign it.
204+ // It keeps the default values if they're not in the JSON file.
205+ err := readJsonc (setupFile , & setup )
206+ if err != nil {
207+ return nil , err
208+ }
209+ }
210+ setups [path ] = setup
211+ }
212+ return & setups , nil
213+ }
0 commit comments