Skip to content

Commit 5ba5ee9

Browse files
committed
Allow import from remote source (#46)
1 parent 92313b6 commit 5ba5ee9

5 files changed

Lines changed: 107 additions & 7 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
examples/*
22
!examples/*.tsh
3+
ext

parser/parser.go

Lines changed: 85 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import (
44
"crypto/sha256"
55
"errors"
66
"fmt"
7+
"io"
78
"maps"
9+
"net/http"
810
"os"
911
"path/filepath"
1012
"slices"
@@ -427,6 +429,33 @@ func buildPrefixedName(prefix string, funcName string) string {
427429
return funcName
428430
}
429431

432+
func updateExtInfo(infoPath string, remotePath string, localPath string) error {
433+
var lines = []string{}
434+
435+
// If info file doesn't exist yet, create it.
436+
if _, err := os.Stat(infoPath); err != nil {
437+
lines = append(lines, "| Remote | Local |", "|-|-|")
438+
} else {
439+
contentBytes, err := os.ReadFile(infoPath)
440+
441+
if err != nil {
442+
return err
443+
}
444+
content := string(contentBytes)
445+
lines = strings.Split(content, "\n")
446+
}
447+
448+
i := slices.IndexFunc(lines, func(line string) bool {
449+
return strings.Contains(line, remotePath)
450+
})
451+
452+
// If entry is new, add it.
453+
if i < 0 {
454+
lines = append(lines, fmt.Sprintf("| %s | %s |", remotePath, localPath))
455+
}
456+
return os.WriteFile(infoPath, []byte(strings.Join(lines, "\n")), 0700)
457+
}
458+
430459
func (p *Parser) atError(what string, token lexer.Token) error {
431460
return fmt.Errorf("%s at row %d, column %d: %s", what, token.Row(), token.Column(), p.path)
432461
}
@@ -763,12 +792,67 @@ func (p *Parser) evaluateImports(ctx context) ([]Statement, error) {
763792
for {
764793
imp, err := p.evaluateImport()
765794

795+
if err != nil {
796+
return nil, err
797+
}
798+
ex, err := os.Executable()
799+
766800
if err != nil {
767801
return nil, err
768802
}
769803
path := imp.path
770804
alias := imp.alias
771805
absPath := path
806+
sourceLocation := "local"
807+
808+
// If path is a remote address, download file to executable path and
809+
// change path to the loaded path.
810+
if strings.Contains(path, "://") {
811+
sourceLocation = "remote"
812+
externalPath := filepath.Join(filepath.Dir(ex), "ext")
813+
err = os.MkdirAll(externalPath, 0700)
814+
815+
if err != nil {
816+
return nil, err
817+
}
818+
hash := sha256.New()
819+
_, err = hash.Write([]byte(path))
820+
821+
if err != nil {
822+
return nil, err
823+
}
824+
hashString := ""
825+
826+
for _, b := range hash.Sum(nil) {
827+
hashString = fmt.Sprintf("%s%02x", hashString, b)
828+
}
829+
absPath = filepath.Join(externalPath, fmt.Sprintf("%s.tsh", hashString))
830+
831+
// If file doesn't exist, download it.
832+
if _, err = os.Stat(absPath); err != nil {
833+
resp, err := http.Get(path)
834+
835+
if err != nil {
836+
return nil, err
837+
}
838+
defer resp.Body.Close() // Make sure stream gets closed.
839+
bodyBytes, err := io.ReadAll(resp.Body)
840+
841+
if err != nil {
842+
return nil, err
843+
}
844+
err = os.WriteFile(absPath, bodyBytes, 0400)
845+
846+
if err != nil {
847+
return nil, err
848+
}
849+
err = updateExtInfo(filepath.Join(externalPath, "info.md"), path, absPath)
850+
851+
if err != nil {
852+
return nil, err
853+
}
854+
}
855+
}
772856

773857
// If path is relative, create an absolute path by combining the loaded path with the import path.
774858
if !filepath.IsAbs(absPath) {
@@ -778,11 +862,6 @@ func (p *Parser) evaluateImports(ctx context) ([]Statement, error) {
778862

779863
// If path doesn't exist, try to find it in the standard library.
780864
if _, err := os.Stat(absPath); err != nil {
781-
ex, err := os.Executable()
782-
783-
if err != nil {
784-
return nil, err
785-
}
786865
pathWithoutExt := strings.TrimSuffix(path, filepath.Ext(path))
787866
absPathTemp := filepath.Join(filepath.Dir(ex), "std", fmt.Sprintf("%s.tsh", pathWithoutExt)) // Standart library is at <executable-path>/std.
788867

@@ -796,7 +875,7 @@ func (p *Parser) evaluateImports(ctx context) ([]Statement, error) {
796875
}
797876
} else if aliasLen == 0 {
798877
// If it's not a standard library path, an alias must be provided.
799-
return nil, fmt.Errorf(`an alias must be provided for the local import "%s" in "%s"`, path, p.path)
878+
return nil, fmt.Errorf(`an alias must be provided for the %s import "%s" in %s`, sourceLocation, path, p.path)
800879
}
801880
importParser := New()
802881
importedProg, err := importParser.parse(absPath, true)

tests/import.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ func testMultiImportSuccess(t *testing.T, transpilerFunc transpilerCalloutFunc)
4747
})
4848
}
4949

50-
5150
func testWildlyMixedImportsSuccess(t *testing.T, transpilerFunc transpilerCalloutFunc) {
5251
transpilerFunc(t, func(dir string) (string, error) {
5352
return `import (
@@ -70,3 +69,16 @@ func testWildlyMixedImportsSuccess(t *testing.T, transpilerFunc transpilerCallou
7069
require.Equal(t, "1\n1\n1\n0\n0", output)
7170
})
7271
}
72+
73+
func testImportsFromExternalSourceSuccess(t *testing.T, transpilerFunc transpilerCalloutFunc) {
74+
transpilerFunc(t, func(dir string) (string, error) {
75+
return `
76+
import strings "https://raw.githubusercontent.com/monstermichl/TypeShell/refs/heads/main/std/strings.tsh"
77+
78+
print(strings.Contains("Hello World", "Hel"))
79+
`, nil
80+
}, func(output string, err error) {
81+
require.Nil(t, err)
82+
require.Equal(t, "1", output)
83+
})
84+
}

tests/import_linux_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,7 @@ func TestMultiImportSuccess(t *testing.T) {
1919
func TestWildlyMixedImportsSuccess(t *testing.T) {
2020
testWildlyMixedImportsSuccess(t, transpileBashFunc)
2121
}
22+
23+
func TestImportsFromExternalSourceSuccess(t *testing.T) {
24+
testImportsFromExternalSourceSuccess(t, transpileBashFunc)
25+
}

tests/import_windows_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,7 @@ func TestMultiImportSuccess(t *testing.T) {
1919
func TestWildlyMixedImportsSuccess(t *testing.T) {
2020
testWildlyMixedImportsSuccess(t, transpileBatchFunc)
2121
}
22+
23+
func TestImportsFromExternalSourceSuccess(t *testing.T) {
24+
testImportsFromExternalSourceSuccess(t, transpileBatchFunc)
25+
}

0 commit comments

Comments
 (0)