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+
430459func (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 )
0 commit comments