Skip to content

Commit 3ad17bd

Browse files
committed
feat: allow mocking the filesystem
1 parent d9b4966 commit 3ad17bd

25 files changed

+330
-62
lines changed

app/lithia/cmd/repl.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import (
44
"bufio"
55
"fmt"
66
"io"
7-
"os"
87

98
cobra "github.com/muesli/coral"
109
"github.com/vknabel/lithia"
1110
"github.com/vknabel/lithia/reporting"
11+
"github.com/vknabel/lithia/world"
1212
)
1313

1414
func init() {
@@ -26,12 +26,12 @@ var replCmd = &cobra.Command{
2626
}
2727

2828
func runPrompt() {
29-
importRoot, err := os.Getwd()
29+
importRoot, err := world.Current.FS.Getwd()
3030
if err != nil {
31-
fmt.Fprint(os.Stderr, err)
32-
os.Exit(1)
31+
fmt.Fprint(world.Current.Stderr, err)
32+
world.Current.Env.Exit(1)
3333
}
34-
reader := bufio.NewReader(os.Stdin)
34+
reader := bufio.NewReader(world.Current.Stdin)
3535
inter := lithia.NewDefaultInterpreter(importRoot)
3636
for {
3737
fmt.Print("> ")

app/lithia/cmd/run.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ package cmd
22

33
import (
44
"fmt"
5-
"os"
65
"path"
76

87
cobra "github.com/muesli/coral"
98
"github.com/vknabel/lithia"
9+
"github.com/vknabel/lithia/world"
1010
)
1111

1212
func init() {
@@ -23,16 +23,16 @@ var runCmd = &cobra.Command{
2323
}
2424

2525
func runFile(fileName string) {
26-
scriptData, err := os.ReadFile(fileName)
26+
scriptData, err := world.Current.FS.ReadFile(fileName)
2727
if err != nil {
28-
fmt.Fprint(os.Stderr, err)
29-
os.Exit(1)
28+
fmt.Fprint(world.Current.Stderr, err)
29+
world.Current.Env.Exit(1)
3030
}
3131
inter := lithia.NewDefaultInterpreter(path.Dir(fileName))
3232
script := string(scriptData) + "\n"
3333
_, err = inter.Interpret(fileName, script)
3434
if err != nil {
35-
fmt.Fprint(os.Stderr, err)
36-
os.Exit(1)
35+
fmt.Fprint(world.Current.Stderr, err)
36+
world.Current.Env.Exit(1)
3737
}
3838
}

app/lithia/main.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@ package main
22

33
import (
44
"fmt"
5-
"os"
65

76
"github.com/vknabel/lithia/app/lithia/cmd"
7+
"github.com/vknabel/lithia/world"
88
)
99

1010
func main() {
1111
err := cmd.Execute()
1212
if err != nil {
13-
fmt.Fprint(os.Stderr, err)
14-
os.Exit(1)
13+
fmt.Fprint(world.Current.Stderr, err)
14+
world.Current.Env.Exit(1)
1515
}
1616
}

external/fs/external-fs.go

+6-5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
"github.com/vknabel/lithia/ast"
77
. "github.com/vknabel/lithia/runtime"
8+
"github.com/vknabel/lithia/world"
89
)
910

1011
var _ ExternalDefinition = ExternalFS{}
@@ -48,7 +49,7 @@ func builtinFsWrite(env *Environment, decl ast.Decl) PreludeExternFunction {
4849
if !ok {
4950
return nil, NewRuntimeErrorf("%s is not a string", contentsValue)
5051
}
51-
writeError := os.WriteFile(string(toPath), []byte(string(contents)), 0644)
52+
writeError := world.Current.FS.WriteFile(string(toPath), []byte(string(contents)), 0644)
5253
if writeError != nil {
5354
return env.MakeDataRuntimeValue("Failure", map[string]Evaluatable{
5455
"error": NewConstantRuntimeValue(PreludeString(writeError.Error())),
@@ -74,7 +75,7 @@ func builtinFsRead(env *Environment, decl ast.Decl) PreludeExternFunction {
7475
if !ok {
7576
return nil, NewRuntimeErrorf("%s is not a string", fromPath)
7677
}
77-
bytes, writeError := os.ReadFile(string(fromPath))
78+
bytes, writeError := world.Current.FS.ReadFile(string(fromPath))
7879
if writeError != nil {
7980
return env.MakeDataRuntimeValue("Failure", map[string]Evaluatable{
8081
"error": NewConstantRuntimeValue(PreludeString(writeError.Error())),
@@ -100,7 +101,7 @@ func builtinFsExists(env *Environment, decl ast.Decl) PreludeExternFunction {
100101
if !ok {
101102
return nil, NewRuntimeErrorf("%s is not a string", atPath)
102103
}
103-
_, writeError := os.Stat(string(atPath))
104+
_, writeError := world.Current.FS.Stat(string(atPath))
104105
if os.IsNotExist(writeError) {
105106
return env.MakeEmptyDataRuntimeValue("False")
106107
} else {
@@ -122,7 +123,7 @@ func builtinFsDelete(env *Environment, decl ast.Decl) PreludeExternFunction {
122123
if !ok {
123124
return nil, NewRuntimeErrorf("%s is not a string", atPath)
124125
}
125-
writeError := os.Remove(string(atPath))
126+
writeError := world.Current.FS.Remove(string(atPath))
126127
if writeError != nil {
127128
return env.MakeDataRuntimeValue("Failure", map[string]Evaluatable{
128129
"error": NewConstantRuntimeValue(PreludeString(writeError.Error())),
@@ -148,7 +149,7 @@ func builtinFsDeleteAll(env *Environment, decl ast.Decl) PreludeExternFunction {
148149
if !ok {
149150
return nil, NewRuntimeErrorf("%s is not a string", atPath)
150151
}
151-
writeError := os.Remove(string(atPath))
152+
writeError := world.Current.FS.Remove(string(atPath))
152153
if writeError != nil {
153154
return env.MakeDataRuntimeValue("Failure", map[string]Evaluatable{
154155
"error": NewConstantRuntimeValue(PreludeString(writeError.Error())),

external/os/external-os.go

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
package os
22

33
import (
4-
"os"
5-
64
"github.com/vknabel/lithia/ast"
75
. "github.com/vknabel/lithia/runtime"
6+
"github.com/vknabel/lithia/world"
87
)
98

109
var _ ExternalDefinition = ExternalOS{}
@@ -31,7 +30,7 @@ func builtinOsExit(decl ast.Decl) PreludeExternFunction {
3130
return nil, err
3231
}
3332
if code, ok := value.(PreludeInt); ok {
34-
os.Exit(int(code))
33+
world.Current.Env.Exit(int(code))
3534
return value, nil
3635
} else {
3736
return nil, NewRuntimeErrorf("%s is not an int", value).CascadeDecl(decl)
@@ -49,7 +48,7 @@ func builtinOsEnv(prelude *Environment, decl ast.Decl) PreludeExternFunction {
4948
return nil, err.CascadeDecl(decl)
5049
}
5150
if key, ok := value.(PreludeString); ok {
52-
if env, ok := os.LookupEnv(string(key)); ok && env != "" {
51+
if env, ok := world.Current.Env.LookupEnv(string(key)); ok && env != "" {
5352
value, err := prelude.MakeDataRuntimeValue("Some", map[string]Evaluatable{
5453
"value": NewConstantRuntimeValue(PreludeString(env)),
5554
})

langsrv/handler-initialized.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@ package langsrv
22

33
import (
44
"path"
5-
"path/filepath"
65
"strings"
76

87
"github.com/tliron/glsp"
98
protocol "github.com/tliron/glsp/protocol_3_16"
9+
"github.com/vknabel/lithia/world"
1010
)
1111

1212
func initialized(context *glsp.Context, params *protocol.InitializedParams) error {
1313
for _, root := range ls.workspaceRoots {
14-
matches, err := filepath.Glob(path.Join(strings.TrimPrefix("file://", root), "*/*.lithia"))
14+
matches, err := world.Current.FS.Glob(path.Join(strings.TrimPrefix("file://", root), "*/*.lithia"))
1515
if err != nil {
1616
ls.server.Log.Errorf("package detection failed, due %s", err)
1717
continue

langsrv/handler-text-document-did-open.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ package langsrv
22

33
import (
44
"fmt"
5-
"os"
65

76
"github.com/tliron/glsp"
87
protocol "github.com/tliron/glsp/protocol_3_16"
98
"github.com/vknabel/lithia/ast"
109
"github.com/vknabel/lithia/parser"
1110
"github.com/vknabel/lithia/resolution"
11+
"github.com/vknabel/lithia/world"
1212
)
1313

1414
func textDocumentDidOpen(context *glsp.Context, params *protocol.DidOpenTextDocumentParams) error {
@@ -47,7 +47,7 @@ func openModuleTextDocumentsIfNeeded(context *glsp.Context, mod resolution.Resol
4747
lithiaParser := parser.NewParser()
4848

4949
syntaxErrs := make([]parser.SyntaxError, 0)
50-
bytes, err := os.ReadFile(filePath)
50+
bytes, err := world.Current.FS.ReadFile(filePath)
5151
if err != nil {
5252
ls.server.Log.Errorf("failed to read %s, due %s", fileUri, err.Error())
5353
continue

langsrv/lang-server.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ type lithiaLangserver struct {
2424
}
2525

2626
var ls lithiaLangserver = lithiaLangserver{
27-
resolver: resolution.DefaultModuleResolver(),
27+
resolver: resolution.NewDefaultModuleResolver(),
2828
documentCache: &documentCache{documents: make(map[protocol.URI]*textDocumentEntry)},
2929
workspaceRoots: []string{},
3030
}

reporting/reporting.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,20 @@ package reporting
22

33
import (
44
"fmt"
5-
"os"
5+
6+
"github.com/vknabel/lithia/world"
67
)
78

89
func ReportErrorOrPanic(err error) {
9-
fmt.Fprintln(os.Stderr, err)
10+
fmt.Fprintln(world.Current.Stderr, err)
1011
}
1112

1213
func ReportError(line int, message string) {
1314
report(line, "", message)
1415
}
1516

1617
func report(line int, where string, message string) {
17-
fmt.Fprintln(os.Stderr, "[line"+fmt.Sprint(line)+"] Error"+where+": "+message)
18+
fmt.Fprintln(world.Current.Stderr, "[line"+fmt.Sprint(line)+"] Error"+where+": "+message)
1819
}
1920

2021
type LocatableError interface {

resolution/module-resolver.go

+20-13
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
package resolution
22

33
import (
4-
"os"
54
"path"
65
"path/filepath"
76
"strings"
87

98
"github.com/vknabel/lithia/ast"
9+
"github.com/vknabel/lithia/world"
1010
)
1111

1212
type ModuleResolver struct {
@@ -20,7 +20,7 @@ type ModuleResolver struct {
2020
lithiaSourceGlob string
2121
}
2222

23-
func DefaultModuleResolver(importRoots ...string) ModuleResolver {
23+
func NewDefaultModuleResolver(importRoots ...string) ModuleResolver {
2424
return ModuleResolver{
2525
externalImportRoots: defaultImportRoots(importRoots...),
2626
defaultPackageName: "root",
@@ -54,13 +54,13 @@ type PackageManifest struct {
5454

5555
func defaultImportRoots(importRoots ...string) []string {
5656
roots := importRoots
57-
if path, ok := os.LookupEnv("LITHIA_LOCALS"); ok {
57+
if path, ok := world.Current.Env.LookupEnv("LITHIA_LOCALS"); ok {
5858
roots = append(roots, path)
5959
}
60-
if path, ok := os.LookupEnv("LITHIA_PACKAGES"); ok {
60+
if path, ok := world.Current.Env.LookupEnv("LITHIA_PACKAGES"); ok {
6161
roots = append(roots, path)
6262
}
63-
if path, ok := os.LookupEnv("LITHIA_STDLIB"); ok {
63+
if path, ok := world.Current.Env.LookupEnv("LITHIA_STDLIB"); ok {
6464
roots = append(roots, path)
6565
} else {
6666
roots = append(roots, "/usr/local/opt/lithia/stdlib")
@@ -81,7 +81,7 @@ func (mr *ModuleResolver) ResolvePackageForReferenceFile(referenceFile string) R
8181
referenceFile = removingFilePrefix(referenceFile)
8282
for _, candidates := range mr.manifestSearchPaths {
8383
manifestPath := filepath.Join(path.Dir(referenceFile), candidates, mr.manifestName)
84-
if _, err := os.Stat(manifestPath); err == nil {
84+
if _, err := world.Current.FS.Stat(manifestPath); err == nil {
8585
packagePath := path.Dir(manifestPath)
8686
return ResolvedPackage{
8787
Name: mr.defaultPackageName,
@@ -92,7 +92,7 @@ func (mr *ModuleResolver) ResolvePackageForReferenceFile(referenceFile string) R
9292
}
9393
}
9494
}
95-
dir, err := os.Getwd()
95+
dir, err := world.Current.FS.Getwd()
9696
if err != nil {
9797
dir = path.Dir(referenceFile)
9898
}
@@ -108,6 +108,11 @@ func (mr *ModuleResolver) ResolvePackageAndModuleForReferenceFile(referenceFile
108108
}
109109
moduleFilepath := filepath.Dir(relativeFile)
110110
moduleParts := strings.Split(moduleFilepath, string(filepath.Separator))
111+
for i := len(moduleParts) - 1; i >= 0; i-- {
112+
if moduleParts[i] == "." {
113+
moduleParts = append(moduleParts[:i], moduleParts[i+1:]...)
114+
}
115+
}
111116
resolvedModule, err := mr.resolveModuleWithinPackage(pkg, moduleParts)
112117
if err != nil {
113118
return mr.CreateSingleFileModule(pkg, referenceFile)
@@ -121,9 +126,11 @@ func (mr *ModuleResolver) AddRootImportPath(path string) {
121126

122127
func (mr *ModuleResolver) CreateSingleFileModule(pkg ResolvedPackage, file string) ResolvedModule {
123128
file = removingFilePrefix(file)
129+
trimmed := strings.TrimSuffix(filepath.Base(file), ".lithia")
130+
uniform := strings.ReplaceAll(trimmed, ".", "_")
124131
return ResolvedModule{
125132
packageRef: &pkg,
126-
relativeName: ast.ModuleName(strings.ReplaceAll(strings.TrimSuffix(filepath.Base(file), ".lithia"), ".", "_")),
133+
relativeName: ast.ModuleName(uniform),
127134
Path: file,
128135
Files: []string{file},
129136
}
@@ -143,10 +150,10 @@ func (mr *ModuleResolver) ResolveModuleFromPackage(pkg ResolvedPackage, moduleNa
143150
searchPaths := append([]string{pkg.Path}, mr.externalImportRoots...)
144151
for _, searchPath := range searchPaths {
145152
packagePath := path.Join(searchPath, packageName)
146-
if info, err := os.Stat(packagePath); err == nil && info.IsDir() {
153+
if info, err := world.Current.FS.Stat(packagePath); err == nil && info.IsDir() {
147154
var match ResolvedPackage
148155
manifestPath := path.Join(packagePath, mr.manifestName)
149-
if _, err := os.Stat(manifestPath); err == nil && !info.IsDir() {
156+
if _, err := world.Current.FS.Stat(manifestPath); err == nil && !info.IsDir() {
150157
match = ResolvedPackage{
151158
Name: packageName,
152159
Path: packagePath,
@@ -165,15 +172,15 @@ func (mr *ModuleResolver) ResolveModuleFromPackage(pkg ResolvedPackage, moduleNa
165172

166173
func (mr *ModuleResolver) resolveModuleWithinPackage(pkg ResolvedPackage, moduleParts []string) (ResolvedModule, error) {
167174
if len(moduleParts) == 0 {
168-
files, err := filepath.Glob(path.Join(pkg.Path, mr.defaultSrcDir, mr.lithiaSourceGlob))
175+
files, err := world.Current.FS.Glob(path.Join(pkg.Path, mr.defaultSrcDir, mr.lithiaSourceGlob))
169176
if len(files) > 0 {
170177
return ResolvedModule{
171178
packageRef: &pkg,
172179
Path: path.Join(pkg.Path, mr.defaultSrcDir),
173180
Files: files,
174181
}, err
175182
}
176-
files, err = filepath.Glob(path.Join(pkg.Path, mr.lithiaSourceGlob))
183+
files, err = world.Current.FS.Glob(path.Join(pkg.Path, mr.lithiaSourceGlob))
177184
return ResolvedModule{
178185
packageRef: &pkg,
179186
Path: pkg.Path,
@@ -182,7 +189,7 @@ func (mr *ModuleResolver) resolveModuleWithinPackage(pkg ResolvedPackage, module
182189
}
183190
pathElems := append([]string{pkg.Path}, moduleParts...)
184191
modulePath := path.Join(pathElems...)
185-
files, err := filepath.Glob(path.Join(modulePath, mr.lithiaSourceGlob))
192+
files, err := world.Current.FS.Glob(path.Join(modulePath, mr.lithiaSourceGlob))
186193
return ResolvedModule{
187194
packageRef: &pkg,
188195
relativeName: ast.ModuleName(strings.Join(moduleParts, ".")),

0 commit comments

Comments
 (0)