// 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 main() { // This turns off focus reporting mode that will print “^[[O” and “^[[I” // when the terminal window gets / loses focus // http://superuser.com/questions/931873/o-and-i-appearing-on-iterm2-when-focus-lost fmt.Print("\033[?1004l") ctx, cancel, err := process.Initialise(context.Background(), nil) if err != nil { fmt.Println(err) os.Exit(1) } env := envctx.FromContext(ctx) cmd := cmdctx.FromContext(ctx) c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt) signal.Notify(c, syscall.SIGTERM) go func() { <-c cancel() }() if err := process.GenerateAll(ctx, env.Path, map[string]bool{}); err != nil { fmt.Println(err.Error()) wgctx.WaitAndExit(ctx, 1) } if cmd.Validate { err := process.RunValidateCommand(ctx) if err != nil { if !cmd.Log { // in log mode, we have already written the output of the exec'ed ke command, // so we don't need to duplicate the error message. if v, ok := kerr.Source(err).(validate.ValidationCommandError); ok { fmt.Println(v.Description) wgctx.WaitAndExit(ctx, 4) // Exit code 4: validation error } fmt.Println(err.Error()) } wgctx.WaitAndExit(ctx, 1) } } if cmd.Edit { if err = server.Start(ctx, cancel); err != nil { fmt.Println(err.Error()) wgctx.WaitAndExit(ctx, 1) } } wgctx.WaitAndExit(ctx, 0) }
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 GoGet(ctx context.Context, path string) error { vos := vosctx.FromContext(ctx) cmd := cmdctx.FromContext(ctx) cmd.Print("Running go get -d ") args := []string{"get", "-d"} if cmd.Update { cmd.Print("-u ") args = append(args, "-u") } cmd.Print(path, "...") args = append(args, path) exe := exec.Command("go", args...) exe.Env = vos.Environ() if combined, err := exe.CombinedOutput(); err != nil { if !strings.Contains(string(combined), "no buildable Go source files") { return kerr.New("NIKCKQAKUI", "%s: %s", err.Error(), combined) } } cmd.Println(" OK.") 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 parse(ctx context.Context, path string, queue []string) (*sysctx.SysPackageInfo, error) { scache := sysctx.FromContext(ctx) cmd := cmdctx.FromContext(ctx) for _, q := range queue { if q == path { return nil, kerr.New("SCSCFJPPHD", "Circular import %v -> %v", queue, path) } } if _, found := scache.Get("kego.io/json"); !found { system.RegisterJsonTypes(ctx) } hash := &PackageHasher{Path: path, Aliases: map[string]string{}, Types: map[string]uint64{}, Exports: map[string]uint64{}} importPackage := func(importPath string, importAlias string) error { if _, found := scache.Get(importPath); !found { _, err := parse(ctx, importPath, append(queue, path)) if err != nil { return kerr.Wrap("RIARRSCMVE", err) } } hash.Aliases[importAlias] = importPath return nil } packageDirectoryExists := true _, err := packages.GetDirFromPackage(ctx, path) if err != nil { _, ok := kerr.Source(err).(gopackages.NotFoundError) if ok { packageDirectoryExists = false } } if !packageDirectoryExists || cmd.Update { if err := GoGet(ctx, path); err != nil { return nil, kerr.Wrap("SBALWXUPKN", err) } } env, err := ScanForEnv(ctx, path) if err != nil { return nil, kerr.Wrap("GJRHNGGWFD", err) } // Always scan the system package first if we don't have it already if path != "kego.io/system" { if err := importPackage("kego.io/system", "system"); err != nil { return nil, kerr.Wrap("ORRCDNUPOX", err) } } for aliasName, aliasPath := range env.Aliases { if aliasPath == "kego.io/system" || aliasName == "system" { return nil, kerr.New("EWMLNJDXKC", "Illegal import %s", aliasName) } if err := importPackage(aliasPath, aliasName); err != nil { return nil, kerr.Wrap("NOVMGYKHHI", err) } } pcache := scache.SetEnv(env) cmd.Printf("Parsing %s...", path) if err := scanForTypesAndExports(ctx, env, pcache, hash); err != nil { return nil, kerr.Wrap("VFUNPHUFHD", err) } cmd.Println(" OK.") h, err := hash.Hash() if err != nil { return nil, kerr.Wrap("MIODRYNEJQ", err) } env.Hash = h return pcache, 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) }