// Generate generates the source code for type structs, and writes the // generated.go to the filesystem. func Generate(ctx context.Context, env *envctx.Env) error { wgctx.Add(ctx, "Generate") defer wgctx.Done(ctx, "Generate") cmd := cmdctx.FromContext(ctx) cmd.Printf("Generating types for %s... ", env.Path) outputDir := env.Dir filename := "generated.go" source, err := generate.Structs(ctx, env) if err != nil { return kerr.Wrap("XFNESBLBTQ", err) } // We only backup in the system structs and types files because they are // the only generated files we ever need to roll back backup := env.Path == "kego.io/system" if err = save(outputDir, source, filename, backup); err != nil { return kerr.Wrap("UONJTTSTWW", err) } else { cmd.Println("OK.") } return nil }
func serve(ctx context.Context) error { wgctx.Add(ctx, "serve") defer wgctx.Done(ctx, "serve") env := envctx.FromContext(ctx) cmd := cmdctx.FromContext(ctx) // Starting with port zero chooses a random open port listner, err := net.Listen("tcp", fmt.Sprintf(":%d", cmd.Port)) if err != nil { return kerr.Wrap("QGLXHWPWQW", err) } defer listner.Close() // Here we get the address we're serving on address, ok := listner.Addr().(*net.TCPAddr) if !ok { return kerr.New("CBLPYVGGUR", "Can't find address (l.Addr() is not *net.TCPAddr)") } url := fmt.Sprintf("http://localhost:%d/%s/", address.Port, env.Path) cmd.Printf("Server now running on %s\n", url) // We open the default browser and navigate to the address we're serving // from. This will error if the system doesn't have a browser, so we can // ignore the error. browser.OpenURL(url) withCancel(ctx, func() { err = http.Serve(listner, nil) }) if err != nil { return kerr.Wrap("TUCBTWMRNN", err) } if cmd.Debug { return kerr.New("ATUTBOICGJ", "Connection closed") } return nil }
// buildValidateCommand creates a temporary folder in the package, in which the go source for the // local command is generated. This command is then compiled. func buildValidateCommand(ctx context.Context) error { wgctx.Add(ctx, "buildValidateCommand") defer wgctx.Done(ctx, "buildValidateCommand") env := envctx.FromContext(ctx) cmd := cmdctx.FromContext(ctx) validateCommandPath := filepath.Join(env.Dir, validateCommand) source, err := generate.ValidateCommand(ctx) if err != nil { return kerr.Wrap("SPRFABSRWK", err) } outputDir, err := ioutil.TempDir(env.Dir, "temporary") if err != nil { return kerr.Wrap("HWOPVXYMCT", err) } defer os.RemoveAll(outputDir) outputName := "generated_cmd.go" outputPath := filepath.Join(outputDir, outputName) if err = save(outputDir, source, outputName, false); err != nil { return kerr.Wrap("FRLCYFOWCJ", err) } cmd.Print("Building validate command... ") combined, stdout, stderr := logger.Logger(cmd.Log) exe := exec.Command("go", "build", "-o", validateCommandPath, outputPath) exe.Stdout = stdout exe.Stderr = stderr if err := exe.Run(); err != nil { return kerr.Wrap("OEPAEEYKIS", err) } cmd.Println("OK.") cmd.Print(combined.String()) return nil }
func script(ctx context.Context, w http.ResponseWriter, req *http.Request, sourceMap bool) error { wgctx.Add(ctx, "script") defer wgctx.Done(ctx, "script") path := "" if strings.HasSuffix(req.URL.Path, ".map") { path = req.URL.Path[1 : len(req.URL.Path)-14] } else { path = req.URL.Path[1 : len(req.URL.Path)-10] } env, err := parser.ScanForEnv(ctx, path) // This is the client code for the editor which we will compile to // Javascript using GopherJs below. source, err := generate.Editor(ctx, env) if err != nil { return kerr.Wrap("UWPDBQXURR", err) } var out []byte withCancel(ctx, func() { out, err = Compile(ctx, source, sourceMap) }) if err != nil { return kerr.Wrap("LSUXHJMSSX", err) } withCancel(ctx, func() { err = writeWithTimeout(w, out) }) if err != nil { return kerr.Wrap("GAOWWBAIAL", err) } return nil }
func Start(ctx context.Context, cancel context.CancelFunc) error { wgctx.Add(ctx, "Start") defer wgctx.Done(ctx, "Start") auth := auther.New() cmd := cmdctx.FromContext(ctx) fail := make(chan error) cmd.Println("Starting editor server... ") // This contains the source map that will be persisted between requests http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { if strings.HasSuffix(req.URL.Path, "/favicon.ico") { w.WriteHeader(404) return } if strings.HasSuffix(req.URL.Path, "/script.js") { if err := script(ctx, w, req, false); err != nil { fail <- kerr.Wrap("XPVTVKDWHJ", err) return } return } if strings.HasSuffix(req.URL.Path, "/script.js.map") { if err := script(ctx, w, req, true); err != nil { fail <- kerr.Wrap("JAIBRHULSI", err) return } return } if err := root(ctx, w, req, auth); err != nil { fail <- kerr.Wrap("QOMJGNOCQF", err) return } }) if err := rpc.Register(&Server{ctx: ctx, auth: auth}); err != nil { return kerr.Wrap("RESBVVGRMH", err) } http.Handle("/_rpc", websocket.Handler(func(ws *websocket.Conn) { ws.PayloadType = websocket.BinaryFrame rpc.ServeConn(ws) })) go func() { if err := serve(ctx); err != nil { fail <- err } }() done := ctx.Done() for { select { case <-done: fmt.Println("Exiting editor server (interupted)... ") return nil case err, open := <-fail: if !open { // Channel has been closed, so app should gracefully exit. cancel() cmd.Println("Exiting editor server (finished)... ") } else { // Error received, so app should display error. // return kerr.New("WKHPTVJBIL", err, "Fail channel receive") fmt.Println(err) } if !cmd.Debug { return nil } } } }
func root(ctx context.Context, w http.ResponseWriter, req *http.Request, auth auther.Auther) error { wgctx.Add(ctx, "root") defer wgctx.Done(ctx, "root") if b, err := static.Asset(req.URL.Path[1:]); err == nil { if strings.HasSuffix(req.URL.Path, ".css") { w.Header().Set("Content-Type", "text/css") } if err := writeWithTimeout(w, b); err != nil { return kerr.Wrap("PVPCXBIUJT", err) } return nil } path := req.URL.Path[1:] if strings.HasSuffix(path, "/") { path = path[0 : len(path)-1] } // use a new context with a blank sysctx for the duration of this function // to prevent caching ctx = sysctx.NewContext(ctx) scache := sysctx.FromContext(ctx) env, err := parser.ScanForEnv(ctx, path) if err != nil { if _, ok := kerr.Source(err).(gopackages.NotFoundError); ok { w.WriteHeader(404) return nil } return kerr.Wrap("ALINBMKDRP", err) } pcache, err := parser.Parse(ctx, path) if err != nil { return kerr.Wrap("HIHWJRPUKE", err) } if err := process.GenerateAll(ctx, env.Path, map[string]bool{}); err != nil { return kerr.Wrap("LVGHABDYNQ", err) } data := map[string]string{} for _, name := range pcache.Globals.Keys() { if g, ok := pcache.Globals.Get(name); ok { data[name] = g.File } } pkgBytes := pcache.PackageBytes pkgFilename := pcache.PackageFilename if pkgBytes == nil { b, err := system.Marshal(ctx, system.EmptyPackage()) if err != nil { return kerr.Wrap("OUBOTYGPKU", err) } pkgBytes = b pkgFilename = "package.ke.json" } imports := map[string]shared.ImportInfo{} var scan func(string) error scan = func(path string) error { if _, ok := imports[path]; ok { return nil } syspi, ok := scache.Get(path) if !ok { return kerr.New("VIGKIUPNCF", "%s not found in sys ctx", path) } info := shared.ImportInfo{ Path: path, Aliases: syspi.Aliases, Types: map[string]shared.TypeInfo{}, } for _, name := range syspi.Files.Keys() { if b, ok := syspi.Files.Get(name); ok { info.Types[name] = shared.TypeInfo{ File: b.File, Bytes: b.Bytes, } } } imports[path] = info for _, child := range syspi.Aliases { if err := scan(child); err != nil { return kerr.Wrap("NCULMUUUOT", err) } } return nil } // First we always import system if err := scan("kego.io/system"); err != nil { return kerr.Wrap("KRXSLOJKWV", err) } if err := scan(env.Path); err != nil { return kerr.Wrap("EELKQDCJGN", err) } info := shared.Info{ Path: env.Path, Aliases: env.Aliases, Data: data, Package: pkgBytes, PackageFilename: pkgFilename, Imports: imports, Hash: auth.Sign([]byte(env.Path)), } buf := bytes.NewBuffer([]byte{}) err = gob.NewEncoder(buf).Encode(info) if err != nil { return kerr.Wrap("OHBYTULHUQ", err) } base64EncodedString := base64.StdEncoding.EncodeToString(buf.Bytes()) attrib := base64EncodedString source := []byte(` <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="/bootstrap/css/bootstrap.min.css"> <link rel="stylesheet" href="/bootstrap/css/bootstrap-theme.min.css"> <link rel="stylesheet" href="/split.css"> <link rel="stylesheet" href="/editors.css"> <link rel="stylesheet" href="/tree.css"> <script src="/jquery-2.2.4.min.js"></script> <script src="/jquery-ui/jquery-ui.min.js"></script> <script src="/split.min.js"></script> <script src="/bootstrap/js/bootstrap.min.js"></script> <link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo="> </head> <body id="body" info="` + attrib + `"></body> <script src="/` + env.Path + `/script.js"></script> </html>`) if err := writeWithTimeout(w, source); err != nil { return kerr.Wrap("ICJSAIMDRF", err) } return nil }
func runValidateCommand(ctx context.Context, build bool, repeat bool) (err error) { wgctx.Add(ctx, "runValidateCommand") defer wgctx.Done(ctx, "runValidateCommand") if build { if err := buildValidateCommand(ctx); err != nil { return kerr.Wrap("FIJHGMEEUM", err) } } env := envctx.FromContext(ctx) cmd := cmdctx.FromContext(ctx) validateCommandPath := filepath.Join(env.Dir, validateCommand) cmd.Print("Running validate command... ") combined, stdout, stderr := logger.Logger(cmd.Log) hashChanged := false exe := exec.Command(validateCommandPath) exe.Stdout = stdout exe.Stderr = stderr if err = exe.Run(); err != nil { exiterr, ok := err.(*exec.ExitError) if !ok { goto Repeat } // The program has exited with an exit code != 0. This works on both Unix and Windows. // Although package syscall is generally platform dependent, WaitStatus is defined for // both Unix and Windows and in both cases has an ExitStatus() method with the same // signature. status, ok := exiterr.Sys().(syscall.WaitStatus) if !ok { // ke: {"block": {"notest": true}} goto Repeat } switch status.ExitStatus() { case 3: // Exit status 3 = hash changed hashChanged = true goto Repeat case 4: // Exit status 4 = validation error return validate.ValidationCommandError{Struct: kerr.New("ETWHPXTUVB", strings.TrimSpace(combined.String()))} default: // ke: {"block": {"notest": true}} goto Repeat } } cmd.Println("OK.") return nil Repeat: if repeat { if hashChanged { cmd.Println("Types have changed since last run. Rebuilding...") } else { // ke: {"block": {"notest": true}} cmd.Println("Command returned an error. Rebuilding...") } if err := runValidateCommand(ctx, true, false); err != nil { return kerr.Wrap("HOHQEISLMI", err) } return nil } return kerr.Wrap("DTTHRRJSSF", err) }