github.com/xyproto/orbiton/v2@v2.65.12-0.20240516144430-e10a419274ec/filesearch.go (about)

     1  package main
     2  
     3  import (
     4  	"errors"
     5  	"os"
     6  	"path/filepath"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/xyproto/files"
    11  )
    12  
    13  // Don't search for a corresponding header/source file for longer than ~0.5 seconds
    14  var fileSearchMaxTime = 500 * time.Millisecond
    15  
    16  // ExtFileSearch will search for a corresponding file, given a slice of extensions.
    17  // This is useful for ie. finding a corresponding .h file for a .c file.
    18  // The search starts in the current directory, then searches every parent directory in depth.
    19  // TODO: Search sibling and parent directories named "include" first, then search the rest.
    20  func ExtFileSearch(absCppFilename string, headerExtensions []string, maxTime time.Duration) (string, error) {
    21  	cppBasename := filepath.Base(absCppFilename)
    22  	searchPath := filepath.Dir(absCppFilename)
    23  	ext := filepath.Ext(cppBasename)
    24  	if ext == "" {
    25  		return "", errors.New("filename has no extension: " + cppBasename)
    26  	}
    27  	firstName := cppBasename[:len(cppBasename)-len(ext)]
    28  
    29  	// First search the same path as the given filename, without using Walk
    30  	withoutExt := strings.TrimSuffix(absCppFilename, ext)
    31  	for _, hext := range headerExtensions {
    32  		if files.Exists(withoutExt + hext) {
    33  			return withoutExt + hext, nil
    34  		}
    35  	}
    36  
    37  	var headerNames []string
    38  	for _, ext := range headerExtensions {
    39  		headerNames = append(headerNames, firstName+ext)
    40  	}
    41  	foundHeaderAbsPath := ""
    42  	startTime := time.Now()
    43  	for {
    44  		err := filepath.Walk(searchPath, func(path string, info os.FileInfo, err error) error {
    45  			basename := filepath.Base(info.Name())
    46  			if err == nil {
    47  				// logf("Walking %s\n", path)
    48  				for _, headerName := range headerNames {
    49  					if time.Since(startTime) > maxTime {
    50  						return errors.New("file search timeout")
    51  					}
    52  					if basename == headerName {
    53  						// Found the corresponding header!
    54  						absFilename, err := filepath.Abs(path)
    55  						if err != nil {
    56  							continue
    57  						}
    58  						foundHeaderAbsPath = absFilename
    59  						// logf("Found %s!\n", absFilename)
    60  						return nil
    61  					}
    62  				}
    63  			}
    64  			// No result
    65  			return nil
    66  		})
    67  		if err != nil {
    68  			return "", errors.New("error when searching for a corresponding header for " + cppBasename + ":" + err.Error())
    69  		}
    70  		if len(foundHeaderAbsPath) == 0 {
    71  			// Try the parent directory
    72  			searchPath = filepath.Dir(searchPath)
    73  			if len(searchPath) > 2 {
    74  				continue
    75  			}
    76  		}
    77  		break
    78  	}
    79  	if len(foundHeaderAbsPath) == 0 {
    80  		return "", errors.New("found no corresponding header for " + cppBasename)
    81  	}
    82  
    83  	// Return the result
    84  	return foundHeaderAbsPath, nil
    85  }