diff --git a/README.md b/README.md index ddd41cb923..f53bca5346 100644 --- a/README.md +++ b/README.md @@ -2,5 +2,14 @@ [![Build Status](https://travis-ci.com/propelml/deno.svg?token=eWz4oGVxypBGsz78gdKp&branch=master)](https://travis-ci.com/propelml/deno) -A simpler JavaScript runtime +An opinionated JavaScript runtime +```bash +make deno # Builds the deno executable + +make test # Runs the tests. + +make fmt # Formats the code. + +make clean # Cleans the build. +``` diff --git a/deno_dir.go b/deno_dir.go new file mode 100644 index 0000000000..8412889363 --- /dev/null +++ b/deno_dir.go @@ -0,0 +1,102 @@ +package main + +import ( + "crypto/md5" + "encoding/hex" + "github.com/ry/v8worker2" + "io" + "io/ioutil" + "net/http" + "os" + "path" + "runtime" + "strings" +) + +func SourceCodeHash(filename string, sourceCodeBuf []byte) string { + h := md5.New() + h.Write([]byte(filename)) + h.Write(sourceCodeBuf) + return hex.EncodeToString(h.Sum(nil)) +} + +func CacheFileName(filename string, sourceCodeBuf []byte) string { + cacheKey := SourceCodeHash(filename, sourceCodeBuf) + return path.Join(CompileDir, cacheKey+".js") +} + +// Fetches a remoteUrl but also caches it to the localFilename. +func FetchRemoteSource(remoteUrl string, localFilename string) ([]byte, error) { + //println("FetchRemoteSource", remoteUrl) + Assert(strings.HasPrefix(localFilename, SrcDir), localFilename) + var sourceReader io.Reader + + file, err := os.Open(localFilename) + if *flagReload || os.IsNotExist(err) { + // Fetch from HTTP. + println("Downloading", remoteUrl) + res, err := http.Get(remoteUrl) + if err != nil { + return nil, err + } + defer res.Body.Close() + + err = os.MkdirAll(path.Dir(localFilename), 0700) + if err != nil { + return nil, err + } + + // Write to to file. Need to reopen it for writing. + file, err = os.OpenFile(localFilename, os.O_RDWR|os.O_CREATE, 0700) + if err != nil { + return nil, err + } + sourceReader = io.TeeReader(res.Body, file) // Fancy! + + } else if err != nil { + return nil, err + } else { + sourceReader = file + } + defer file.Close() + return ioutil.ReadAll(sourceReader) +} + +func LoadOutputCodeCache(filename string, sourceCodeBuf []byte) (outputCode string, err error) { + cacheFn := CacheFileName(filename, sourceCodeBuf) + outputCodeBuf, err := ioutil.ReadFile(cacheFn) + if os.IsNotExist(err) { + err = nil // Ignore error if we can't load the cache. + } else if err != nil { + outputCode = string(outputCodeBuf) + } + return +} + +func UserHomeDir() string { + if runtime.GOOS == "windows" { + home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") + if home == "" { + home = os.Getenv("USERPROFILE") + } + return home + } + return os.Getenv("HOME") +} + +func loadAsset(w *v8worker2.Worker, path string) { + data, err := Asset(path) + check(err) + err = w.Load(path, string(data)) + check(err) +} + +func createDirs() { + DenoDir = path.Join(UserHomeDir(), ".deno") + CompileDir = path.Join(DenoDir, "compile") + err := os.MkdirAll(CompileDir, 0700) + check(err) + SrcDir = path.Join(DenoDir, "src") + err = os.MkdirAll(SrcDir, 0700) + check(err) +} diff --git a/handlers.go b/handlers.go new file mode 100644 index 0000000000..5f9470ab19 --- /dev/null +++ b/handlers.go @@ -0,0 +1,119 @@ +package main + +import ( + "github.com/golang/protobuf/proto" + "io/ioutil" + "os" + "strings" + "time" +) + +const assetPrefix string = "/$asset$/" + +func recv(buf []byte) []byte { + msg := &Msg{} + err := proto.Unmarshal(buf, msg) + check(err) + switch msg.Payload.(type) { + case *Msg_Exit: + payload := msg.GetExit() + os.Exit(int(payload.Code)) + case *Msg_SourceCodeFetch: + payload := msg.GetSourceCodeFetch() + return HandleSourceCodeFetch(payload.ModuleSpecifier, payload.ContainingFile) + case *Msg_SourceCodeCache: + payload := msg.GetSourceCodeCache() + return HandleSourceCodeCache(payload.Filename, payload.SourceCode, + payload.OutputCode) + case *Msg_TimerStart: + payload := msg.GetTimerStart() + return HandleTimerStart(payload.Id, payload.Interval, payload.Duration) + default: + panic("Unexpected message") + } + + return nil +} + +func HandleSourceCodeFetch(moduleSpecifier string, containingFile string) (out []byte) { + Assert(moduleSpecifier != "", "moduleSpecifier shouldn't be empty") + res := &Msg{} + var sourceCodeBuf []byte + var err error + + defer func() { + if err != nil { + res.Error = err.Error() + } + out, err = proto.Marshal(res) + check(err) + }() + + moduleName, filename, err := ResolveModule(moduleSpecifier, containingFile) + if err != nil { + return + } + + //println("HandleSourceCodeFetch", "moduleSpecifier", moduleSpecifier, + // "containingFile", containingFile, "filename", filename) + + if IsRemote(moduleName) { + sourceCodeBuf, err = FetchRemoteSource(moduleName, filename) + } else if strings.HasPrefix(moduleName, assetPrefix) { + f := strings.TrimPrefix(moduleName, assetPrefix) + sourceCodeBuf, err = Asset("dist/" + f) + } else { + Assert(moduleName == filename, + "if a module isn't remote, it should have the same filename") + sourceCodeBuf, err = ioutil.ReadFile(moduleName) + } + if err != nil { + return + } + + outputCode, err := LoadOutputCodeCache(filename, sourceCodeBuf) + if err != nil { + return + } + + res.Payload = &Msg_SourceCodeFetchRes{ + SourceCodeFetchRes: &SourceCodeFetchResMsg{ + ModuleName: moduleName, + Filename: filename, + SourceCode: string(sourceCodeBuf), + OutputCode: outputCode, + }, + } + return +} + +func HandleSourceCodeCache(filename string, sourceCode string, + outputCode string) []byte { + + fn := CacheFileName(filename, []byte(sourceCode)) + outputCodeBuf := []byte(outputCode) + err := ioutil.WriteFile(fn, outputCodeBuf, 0600) + res := &Msg{} + if err != nil { + res.Error = err.Error() + } + out, err := proto.Marshal(res) + check(err) + return out +} + +func HandleTimerStart(id int32, interval bool, duration int32) []byte { + wg.Add(1) + go func() { + defer wg.Done() + time.Sleep(time.Duration(duration) * time.Millisecond) + resChan <- &Msg{ + Payload: &Msg_TimerReady{ + TimerReady: &TimerReadyMsg{ + Id: id, + }, + }, + } + }() + return nil +} diff --git a/main.go b/main.go index d299a48abb..1c2a99720d 100644 --- a/main.go +++ b/main.go @@ -1,22 +1,14 @@ package main import ( - "crypto/md5" - "encoding/hex" "flag" "fmt" "github.com/golang/protobuf/proto" "github.com/ry/v8worker2" - "io" - "io/ioutil" - "net/http" "net/url" "os" "path" - "runtime" - "strings" "sync" - "time" ) var flagReload = flag.Bool("reload", false, "Reload cached remote source code.") @@ -30,61 +22,6 @@ var SrcDir string var wg sync.WaitGroup var resChan chan *Msg -func SourceCodeHash(filename string, sourceCodeBuf []byte) string { - h := md5.New() - h.Write([]byte(filename)) - h.Write(sourceCodeBuf) - return hex.EncodeToString(h.Sum(nil)) -} - -func CacheFileName(filename string, sourceCodeBuf []byte) string { - cacheKey := SourceCodeHash(filename, sourceCodeBuf) - return path.Join(CompileDir, cacheKey+".js") -} - -func IsRemote(filename string) bool { - u, err := url.Parse(filename) - check(err) - return u.IsAbs() -} - -// Fetches a remoteUrl but also caches it to the localFilename. -func FetchRemoteSource(remoteUrl string, localFilename string) ([]byte, error) { - //println("FetchRemoteSource", remoteUrl) - Assert(strings.HasPrefix(localFilename, SrcDir), localFilename) - var sourceReader io.Reader - - file, err := os.Open(localFilename) - if *flagReload || os.IsNotExist(err) { - // Fetch from HTTP. - println("Downloading", remoteUrl) - res, err := http.Get(remoteUrl) - if err != nil { - return nil, err - } - defer res.Body.Close() - - err = os.MkdirAll(path.Dir(localFilename), 0700) - if err != nil { - return nil, err - } - - // Write to to file. Need to reopen it for writing. - file, err = os.OpenFile(localFilename, os.O_RDWR|os.O_CREATE, 0700) - if err != nil { - return nil, err - } - sourceReader = io.TeeReader(res.Body, file) // Fancy! - - } else if err != nil { - return nil, err - } else { - sourceReader = file - } - defer file.Close() - return ioutil.ReadAll(sourceReader) -} - func ResolveModule(moduleSpecifier string, containingFile string) ( moduleName string, filename string, err error) { moduleUrl, err := url.Parse(moduleSpecifier) @@ -105,161 +42,6 @@ func ResolveModule(moduleSpecifier string, containingFile string) ( return } -const assetPrefix string = "/$asset$/" - -func HandleSourceCodeFetch(moduleSpecifier string, containingFile string) (out []byte) { - Assert(moduleSpecifier != "", "moduleSpecifier shouldn't be empty") - res := &Msg{} - var sourceCodeBuf []byte - var err error - - defer func() { - if err != nil { - res.Error = err.Error() - } - out, err = proto.Marshal(res) - check(err) - }() - - moduleName, filename, err := ResolveModule(moduleSpecifier, containingFile) - if err != nil { - return - } - - //println("HandleSourceCodeFetch", "moduleSpecifier", moduleSpecifier, - // "containingFile", containingFile, "filename", filename) - - if IsRemote(moduleName) { - sourceCodeBuf, err = FetchRemoteSource(moduleName, filename) - } else if strings.HasPrefix(moduleName, assetPrefix) { - f := strings.TrimPrefix(moduleName, assetPrefix) - sourceCodeBuf, err = Asset("dist/" + f) - } else { - Assert(moduleName == filename, - "if a module isn't remote, it should have the same filename") - sourceCodeBuf, err = ioutil.ReadFile(moduleName) - } - if err != nil { - return - } - - outputCode, err := LoadOutputCodeCache(filename, sourceCodeBuf) - if err != nil { - return - } - - res.Payload = &Msg_SourceCodeFetchRes{ - SourceCodeFetchRes: &SourceCodeFetchResMsg{ - ModuleName: moduleName, - Filename: filename, - SourceCode: string(sourceCodeBuf), - OutputCode: outputCode, - }, - } - return -} - -func LoadOutputCodeCache(filename string, sourceCodeBuf []byte) (outputCode string, err error) { - cacheFn := CacheFileName(filename, sourceCodeBuf) - outputCodeBuf, err := ioutil.ReadFile(cacheFn) - if os.IsNotExist(err) { - err = nil // Ignore error if we can't load the cache. - } else if err != nil { - outputCode = string(outputCodeBuf) - } - return -} - -func HandleSourceCodeCache(filename string, sourceCode string, - outputCode string) []byte { - - fn := CacheFileName(filename, []byte(sourceCode)) - outputCodeBuf := []byte(outputCode) - err := ioutil.WriteFile(fn, outputCodeBuf, 0600) - res := &Msg{} - if err != nil { - res.Error = err.Error() - } - out, err := proto.Marshal(res) - check(err) - return out -} - -func HandleTimerStart(id int32, interval bool, duration int32) []byte { - wg.Add(1) - go func() { - defer wg.Done() - time.Sleep(time.Duration(duration) * time.Millisecond) - resChan <- &Msg{ - Payload: &Msg_TimerReady{ - TimerReady: &TimerReadyMsg{ - Id: id, - }, - }, - } - }() - return nil -} - -func UserHomeDir() string { - if runtime.GOOS == "windows" { - home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") - if home == "" { - home = os.Getenv("USERPROFILE") - } - return home - } - return os.Getenv("HOME") -} - -func loadAsset(w *v8worker2.Worker, path string) { - data, err := Asset(path) - check(err) - err = w.Load(path, string(data)) - check(err) -} - -func createDirs() { - DenoDir = path.Join(UserHomeDir(), ".deno") - CompileDir = path.Join(DenoDir, "compile") - err := os.MkdirAll(CompileDir, 0700) - check(err) - SrcDir = path.Join(DenoDir, "src") - err = os.MkdirAll(SrcDir, 0700) - check(err) -} - -func check(e error) { - if e != nil { - panic(e) - } -} - -func recv(buf []byte) []byte { - msg := &Msg{} - err := proto.Unmarshal(buf, msg) - check(err) - switch msg.Payload.(type) { - case *Msg_Exit: - payload := msg.GetExit() - os.Exit(int(payload.Code)) - case *Msg_SourceCodeFetch: - payload := msg.GetSourceCodeFetch() - return HandleSourceCodeFetch(payload.ModuleSpecifier, payload.ContainingFile) - case *Msg_SourceCodeCache: - payload := msg.GetSourceCodeCache() - return HandleSourceCodeCache(payload.Filename, payload.SourceCode, - payload.OutputCode) - case *Msg_TimerStart: - payload := msg.GetTimerStart() - return HandleTimerStart(payload.Id, payload.Interval, payload.Duration) - default: - panic("Unexpected message") - } - - return nil -} - func main() { flag.Parse() args := flag.Args() diff --git a/modules_test.go b/main_test.go similarity index 100% rename from modules_test.go rename to main_test.go diff --git a/util.go b/util.go index 851ee34753..803260920f 100644 --- a/util.go +++ b/util.go @@ -1,7 +1,23 @@ package main +import ( + "net/url" +) + func Assert(cond bool, msg string) { if !cond { panic(msg) } } + +func IsRemote(filename string) bool { + u, err := url.Parse(filename) + check(err) + return u.IsAbs() +} + +func check(e error) { + if e != nil { + panic(e) + } +}