|
| 1 | +package resolution |
| 2 | + |
| 3 | +import ( |
| 4 | + "os" |
| 5 | + "path" |
| 6 | + "path/filepath" |
| 7 | + "strings" |
| 8 | + |
| 9 | + "github.com/vknabel/lithia/ast" |
| 10 | +) |
| 11 | + |
| 12 | +type ModuleResolver struct { |
| 13 | + // each root contains multiple packages |
| 14 | + externalImportRoots []string |
| 15 | + // defaults to Potfile |
| 16 | + defaultPackageName string |
| 17 | + manifestName string |
| 18 | + manifestSearchPaths []string |
| 19 | + defaultSrcDir string |
| 20 | +} |
| 21 | + |
| 22 | +func DefaultModuleResolver() ModuleResolver { |
| 23 | + return ModuleResolver{ |
| 24 | + externalImportRoots: defaultImportRoots(), |
| 25 | + defaultPackageName: "package", |
| 26 | + manifestName: "Potfile", |
| 27 | + manifestSearchPaths: []string{".", "..", "../..", "../../..", "../../../.."}, |
| 28 | + defaultSrcDir: "src", |
| 29 | + } |
| 30 | +} |
| 31 | + |
| 32 | +type ResolvedPackage struct { |
| 33 | + Name string |
| 34 | + Path string |
| 35 | + manifest *packageManifest |
| 36 | +} |
| 37 | + |
| 38 | +type ResolvedModule struct { |
| 39 | + packageRef *ResolvedPackage |
| 40 | + // all modules of this package are relative to this path |
| 41 | + // might contain the package manifest file |
| 42 | + relativeName ast.ModuleName |
| 43 | + Path string |
| 44 | + Files []string |
| 45 | +} |
| 46 | + |
| 47 | +type packageManifest struct { |
| 48 | + // a Potfile-file‚ |
| 49 | + // the package module path will be derived from this location |
| 50 | + path string |
| 51 | +} |
| 52 | + |
| 53 | +func defaultImportRoots() []string { |
| 54 | + roots := []string{} |
| 55 | + if path, ok := os.LookupEnv("LITHIA_LOCALS"); ok { |
| 56 | + roots = append(roots, path) |
| 57 | + } |
| 58 | + if path, ok := os.LookupEnv("LITHIA_PACKAGES"); ok { |
| 59 | + roots = append(roots, path) |
| 60 | + } |
| 61 | + if path, ok := os.LookupEnv("LITHIA_STDLIB"); ok { |
| 62 | + roots = append(roots, path) |
| 63 | + } else { |
| 64 | + roots = append(roots, "/usr/local/opt/lithia/stdlib") |
| 65 | + } |
| 66 | + absoluteImportRoots := make([]string, len(roots)) |
| 67 | + for i, root := range roots { |
| 68 | + absolute, err := filepath.Abs(root) |
| 69 | + if err == nil { |
| 70 | + absoluteImportRoots[i] = absolute |
| 71 | + } else { |
| 72 | + absoluteImportRoots[i] = root |
| 73 | + } |
| 74 | + } |
| 75 | + return absoluteImportRoots |
| 76 | +} |
| 77 | + |
| 78 | +func (mr *ModuleResolver) ResolvePackageForReferenceFile(referenceFile string) ResolvedPackage { |
| 79 | + for _, candidates := range mr.manifestSearchPaths { |
| 80 | + manifestPath := filepath.Join(path.Dir(referenceFile), candidates, mr.manifestName) |
| 81 | + if _, err := os.Stat(manifestPath); err == nil { |
| 82 | + modulePath := path.Dir(manifestPath) |
| 83 | + return ResolvedPackage{ |
| 84 | + Name: mr.defaultPackageName, |
| 85 | + Path: modulePath, |
| 86 | + manifest: &packageManifest{ |
| 87 | + path: manifestPath, |
| 88 | + }, |
| 89 | + } |
| 90 | + } |
| 91 | + } |
| 92 | + dir, err := os.Getwd() |
| 93 | + if err != nil { |
| 94 | + dir = path.Dir(referenceFile) |
| 95 | + } |
| 96 | + return ResolvedPackage{Name: mr.defaultPackageName, Path: dir} |
| 97 | +} |
| 98 | + |
| 99 | +func (mr *ModuleResolver) AddRootImportPath(path string) { |
| 100 | + mr.externalImportRoots = append([]string{path}, mr.externalImportRoots...) |
| 101 | +} |
| 102 | + |
| 103 | +func (mr *ModuleResolver) CreateSingleFileModule(pkg ResolvedPackage, file string) ResolvedModule { |
| 104 | + return ResolvedModule{ |
| 105 | + packageRef: &pkg, |
| 106 | + relativeName: ast.ModuleName(strings.ReplaceAll(strings.TrimSuffix(filepath.Base(file), ".lithia"), ".", "_")), |
| 107 | + Path: file, |
| 108 | + Files: []string{file}, |
| 109 | + } |
| 110 | +} |
| 111 | + |
| 112 | +func (mr *ModuleResolver) ResolveModuleFromPackage(pkg ResolvedPackage, moduleName ast.ModuleName) (ResolvedModule, error) { |
| 113 | + moduleParts := strings.Split(string(moduleName), ".") |
| 114 | + if len(moduleParts) == 0 { |
| 115 | + return mr.resolveModuleWithinPackage(pkg, moduleParts) |
| 116 | + } |
| 117 | + packageName := moduleParts[0] |
| 118 | + packageLevelModuleParts := moduleParts[1:] |
| 119 | + if packageName == mr.defaultPackageName { |
| 120 | + return mr.resolveModuleWithinPackage(pkg, packageLevelModuleParts) |
| 121 | + } |
| 122 | + |
| 123 | + searchPaths := append([]string{pkg.Path}, mr.externalImportRoots...) |
| 124 | + for _, searchPath := range searchPaths { |
| 125 | + packagePath := path.Join(searchPath, packageName) |
| 126 | + if info, err := os.Stat(packagePath); err == nil && info.IsDir() { |
| 127 | + var match ResolvedPackage |
| 128 | + manifestPath := path.Join(packagePath, mr.manifestName) |
| 129 | + if _, err := os.Stat(manifestPath); err == nil && !info.IsDir() { |
| 130 | + match = ResolvedPackage{ |
| 131 | + Name: packageName, |
| 132 | + Path: packagePath, |
| 133 | + manifest: &packageManifest{ |
| 134 | + path: manifestPath, |
| 135 | + }, |
| 136 | + } |
| 137 | + } else { |
| 138 | + match = ResolvedPackage{Name: packageName, Path: packagePath} |
| 139 | + } |
| 140 | + return mr.resolveModuleWithinPackage(match, packageLevelModuleParts) |
| 141 | + } |
| 142 | + } |
| 143 | + return ResolvedModule{}, ModuleNotFoundError{ModuleParts: moduleParts, FromPackage: pkg} |
| 144 | +} |
| 145 | + |
| 146 | +func (mr *ModuleResolver) resolveModuleWithinPackage(pkg ResolvedPackage, moduleParts []string) (ResolvedModule, error) { |
| 147 | + if len(moduleParts) == 0 { |
| 148 | + files, err := filepath.Glob(path.Join(pkg.Path, mr.defaultSrcDir, "*.lithia")) |
| 149 | + if len(files) > 0 { |
| 150 | + return ResolvedModule{ |
| 151 | + packageRef: &pkg, |
| 152 | + Path: pkg.Path, |
| 153 | + Files: files, |
| 154 | + }, err |
| 155 | + } |
| 156 | + files, err = filepath.Glob(path.Join(pkg.Path, "*.lithia")) |
| 157 | + return ResolvedModule{ |
| 158 | + packageRef: &pkg, |
| 159 | + Path: pkg.Path, |
| 160 | + Files: files, |
| 161 | + }, err |
| 162 | + } |
| 163 | + pathElems := append([]string{pkg.Path}, moduleParts...) |
| 164 | + modulePath := path.Join(pathElems...) |
| 165 | + files, err := filepath.Glob(path.Join(modulePath, "*.lithia")) |
| 166 | + return ResolvedModule{ |
| 167 | + packageRef: &pkg, |
| 168 | + relativeName: ast.ModuleName(strings.Join(moduleParts, ".")), |
| 169 | + Path: pkg.Path, |
| 170 | + Files: files, |
| 171 | + }, err |
| 172 | +} |
| 173 | + |
| 174 | +func (mod ResolvedModule) Package() ResolvedPackage { |
| 175 | + return *mod.packageRef |
| 176 | +} |
| 177 | + |
| 178 | +func (mod ResolvedModule) AbsoluteModuleName() ast.ModuleName { |
| 179 | + if mod.relativeName == "" { |
| 180 | + return ast.ModuleName(mod.Package().Name) |
| 181 | + } else { |
| 182 | + return ast.ModuleName(mod.packageRef.Name) + "." + mod.relativeName |
| 183 | + } |
| 184 | +} |
0 commit comments