Files
ConfigKit/internal/engine/filesystem_context.go

177 lines
6.7 KiB
Go

package engine
import (
"io/fs"
"os"
"path"
"path/filepath"
"strings"
)
type resolvedFilesystemContext struct {
native *nativeNamespace
hostToken *namespaceToken
rootPath string
basePath string
mainPath string
}
func resolveFilesystemContext(source Source, context *FilesystemContext, main parsedSource, hostToken *namespaceToken) (resolvedFilesystemContext, *Diagnostic) {
if source.kind == sourceNone && context != nil {
return resolveAbsentFilesystemContext(context)
}
if nativeContext(source, context) {
return resolveNativeFilesystemContext(source, context)
}
return resolveHostFilesystemContext(source, context, main, hostToken)
}
func resolveAbsentFilesystemContext(context *FilesystemContext) (resolvedFilesystemContext, *Diagnostic) {
if context.FS != nil {
return resolveNativeFilesystemContext(Source{kind: sourceNone}, context)
}
rootPath, err := canonicalExistingPath(context.Root)
if err != nil {
value := importDiagnostic(CodeImportInvalid, "configuration root could not be canonicalized", nil)
return resolvedFilesystemContext{}, &value
}
basePath := rootPath
if context.BaseDir != "" {
basePath, err = canonicalExistingPath(context.BaseDir)
if err != nil {
value := importDiagnostic(CodeImportInvalid, "import base directory could not be canonicalized", nil)
return resolvedFilesystemContext{}, &value
}
}
if !containedPath(rootPath, basePath) {
value := importDiagnostic(CodeImportOutsideRoot, "selected source and import base must stay inside the configuration root", nil)
return resolvedFilesystemContext{}, &value
}
return resolvedFilesystemContext{rootPath: rootPath, basePath: basePath}, nil
}
func resolveNativeFilesystemContext(source Source, context *FilesystemContext) (resolvedFilesystemContext, *Diagnostic) {
var namespace *nativeNamespace
resolved := resolvedFilesystemContext{rootPath: ".", basePath: "."}
if source.kind == sourceFilesystem {
namespace = source.namespace
resolved.mainPath = source.path
resolved.basePath = path.Dir(source.path)
if context != nil && context.Root != "" {
resolved.rootPath = context.Root
}
} else if context != nil && context.FS != nil {
namespace = context.FS.namespace
resolved.rootPath, resolved.basePath = context.Root, context.BaseDir
}
if namespace == nil || nilFilesystem(namespace.filesystem) || !fs.ValidPath(resolved.rootPath) || !fs.ValidPath(resolved.basePath) {
value := optionDiagnostic(CodeOptionsInvalid, "filesystem context is invalid")
return resolvedFilesystemContext{}, &value
}
resolved.native = namespace
if !containsNativePath(resolved.rootPath, resolved.basePath) || resolved.mainPath != "" && !containsNativePath(resolved.rootPath, resolved.mainPath) {
value := importDiagnostic(CodeImportOutsideRoot, "selected source and import base must stay inside the configuration root", nil)
return resolvedFilesystemContext{}, &value
}
return resolved, nil
}
func resolveHostFilesystemContext(source Source, context *FilesystemContext, main parsedSource, hostToken *namespaceToken) (resolvedFilesystemContext, *Diagnostic) {
if main.source == nil {
value := importDiagnostic(CodeImportInvalid, "imports require a selected configuration source", nil)
return resolvedFilesystemContext{}, &value
}
resolved := resolvedFilesystemContext{hostToken: hostToken}
if source.kind == sourceFile {
if context != nil && context.BaseDir != "" {
value := optionDiagnostic(CodeOptionsInvalid, "file sources determine their own import base directory")
return resolvedFilesystemContext{}, &value
}
canonical := main.source.Path
if canonical == "" {
var err error
canonical, err = canonicalExistingPath(source.path)
if err != nil {
value := importDiagnostic(CodeImportInvalid, "selected source path could not be canonicalized", nil)
return resolvedFilesystemContext{}, &value
}
}
resolved.mainPath = canonical
resolved.basePath = filepath.Dir(canonical)
resolved.rootPath = resolved.basePath
if context != nil && context.Root != "" {
rootPath, err := canonicalExistingPath(context.Root)
if err != nil {
value := importDiagnostic(CodeImportInvalid, "configuration root could not be canonicalized", nil)
return resolvedFilesystemContext{}, &value
}
resolved.rootPath = rootPath
}
} else {
if context == nil || context.BaseDir == "" || context.Root == "" {
value := optionDiagnostic(CodeOptionsInvalid, "byte and reader imports require absolute base and root directories")
return resolvedFilesystemContext{}, &value
}
if !filepath.IsAbs(context.BaseDir) || !filepath.IsAbs(context.Root) {
value := optionDiagnostic(CodeOptionsInvalid, "byte and reader import base and root directories must be absolute")
return resolvedFilesystemContext{}, &value
}
var err error
resolved.basePath, err = canonicalExistingPath(context.BaseDir)
if err != nil {
value := importDiagnostic(CodeImportInvalid, "import base directory could not be canonicalized", nil)
return resolvedFilesystemContext{}, &value
}
resolved.rootPath, err = canonicalExistingPath(context.Root)
if err != nil {
value := importDiagnostic(CodeImportInvalid, "configuration root could not be canonicalized", nil)
return resolvedFilesystemContext{}, &value
}
}
if !containedPath(resolved.rootPath, resolved.basePath) || resolved.mainPath != "" && !containedPath(resolved.rootPath, resolved.mainPath) {
value := importDiagnostic(CodeImportOutsideRoot, "selected source and import base must stay inside the configuration root", nil)
return resolvedFilesystemContext{}, &value
}
return resolved, nil
}
func containsNativePath(root, name string) bool {
if !fs.ValidPath(name) || !fs.ValidPath(root) {
return false
}
return root == "." || name == root || strings.HasPrefix(name, root+"/")
}
func validateResolvedFilesystemDirectories(resolved resolvedFilesystemContext) *Diagnostic {
for _, directory := range []string{resolved.rootPath, resolved.basePath} {
var info fs.FileInfo
var err error
if resolved.native != nil {
info, err = fs.Stat(resolved.native.filesystem, directory)
} else {
info, err = os.Stat(directory)
}
if err != nil || !info.IsDir() {
value := importDiagnostic(CodeImportInvalid, "configuration root and import base must be directories", nil)
return &value
}
}
return nil
}
func openResolvedFilesystemAccess(resolved resolvedFilesystemContext) (*filesystemAccess, error) {
if resolved.native != nil {
access := newNativeAccess(resolved.native.filesystem, resolved.rootPath, resolved.native.token)
access.label = resolved.native.label
return access, nil
}
access, err := openHostAccess(resolved.rootPath)
if err != nil {
return nil, err
}
if resolved.hostToken != nil {
access.token = resolved.hostToken
}
return access, nil
}