“Talk is cheap, show me your code!”
当一个程序员在做技术分享的时候, 代码演示经常是不可或缺的一个环节。然而在你的演示PPT和代码运行之间切换是一件非常恼人事情,而且非常影响演示的节奏和流畅性。要做一个完美的技术分享,能不能把代码的运行嵌入到PPT中呢?
当然可以,我前不久在公司内部分享了一起关于Python的小谜题,大家可以到斗鱼去观看这次分享的视频录播 (很糟糕的是在36分钟后,摄像头偏移了,拍了大半天挂钟)
为了实现代码嵌入ppt,我用到的关键技术包括:
首先,演示常用的是微软的PowerPoint(至于苹果的Keynote,我暂时还没有找到解决方案),所以你需要安装PowerPoint,在最新的Office套件中,微软提供了一个Webview的插件,有了这个插件,可以直接把一个Web页面,嵌入到Office文档中,自然也就包含了PPT。

注意,为了安全性的考虑,嵌入的web页面必须是https的。
好了有了这个,我们就可以把一个代码运行的web页面直接嵌入到演示文档中,剩下的事情,就是做一个可以运行不同代码的Web应用了。
Glot 是一个开源的在线代码运行平台。其架构如下图所示。

利用Glot的代码运行的核心功能,我们就可以很方便的开发一个可以运行各种代码的web应用。
利用容器,我们可以把整个应用分为以下的几个层次:
- Base
提供基本的代码运行的环境,包含了代码执行的必要的解释器和编译器。在本次演示中,我使用了golang:latest,但是碰巧的是这个镜像是拥有Python的解释器的,我的代码演示都是python,省去了我安装Python的步骤。如果是别的不同的语言的演示,注意安装对应的环境。 - Code Runner https://github.com/gangtao/code_runner
我的Code Runner是对Glot的Code Runner的一个增强,该项目提供一个运行代码的服务。
项目的Dockerfile: FROM golang:latest MAINTAINER gangtao@outlook.com ENV GOPATH=/home/glot ENV GOROOT=/usr/local/go # Add user RUN groupadd glot RUN useradd -m -d /home/glot -g glot -s /bin/bash glot # Copy files Add ./build/release/server /home/glot/ # Add ./vendor/. /home/glot/src USER glot WORKDIR /home/glot/ # generate certificate RUN go run $GOROOT/src/crypto/tls/generate_cert.go --host localhost EXPOSE 8080 # CMD ["/home/glot/runner"] ENTRYPOINT ["/home/glot/server"]
通过Dockerfile我们可以看出该项目主要的内容:利用Glot实现代码运行,产生证书用于https,利用echo实现web 服务。
Web服务的代码如下: package code_runner import ( "fmt" "github.com/prasmussen/glot-code-runner/cmd" "github.com/prasmussen/glot-code-runner/language" "io/ioutil" "os" "path/filepath" ) type Payload struct { Language string `json:"language"` Files []*InMemoryFile `json:"files"` Stdin string `json:"stdin"` Command string `json:"command"` } type InMemoryFile struct { Name string `json:"name"` Content string `json:"content"` } type Result struct { Stdout string `json:"stdout"` Stderr string `json:"stderr"` Error string `json:"error"` } func Run(payload *Payload) *Result { // Ensure that we have at least one file if len(payload.Files) == 0 { exitF("No files given\n") } // Check if we support given language if !language.IsSupported(payload.Language) { exitF("Language '%s' is not supported\n", payload.Language) } // Write files to disk filepaths, err := writeFiles(payload.Files) if err != nil { exitF("Failed to write file to disk (%s)", err.Error()) } var stdout, stderr string // Execute the given command or run the code with // the language runner if no command is given if payload.Command == "" { stdout, stderr, err = language.Run(payload.Language, filepaths, payload.Stdin) } else { workDir := filepath.Dir(filepaths[0]) stdout, stderr, err = cmd.RunBashStdin(workDir, payload.Command, payload.Stdin) } result := &Result{ Stdout: stdout, Stderr: stderr, Error: errToStr(err), } return result } // Writes files to disk, returns list of absolute filepaths func writeFiles(files []*InMemoryFile) ([]string, error) { // Create temp dir tmpPath, err := ioutil.TempDir("", "") if err != nil { return nil, err } paths := make([]string, len(files), len(files)) for i, file := range files { path, err := writeFile(tmpPath, file) if err != nil { return nil, err } paths[i] = path } return paths, nil } // Writes a single file to disk func writeFile(basePath string, file *InMemoryFile) (string, error) { // Get absolute path to file inside basePath absPath := filepath.Join(basePath, file.Name) // Create all parent dirs err := os.MkdirAll(filepath.Dir(absPath), 0775) if err != nil { return "", err } // Write file to disk err = ioutil.WriteFile(absPath, []byte(file.Content), 0664) if err != nil { return "", err } // Return absolute path to file return absPath, nil } func exitF(format string, a ...interface{}) { fmt.Fprintf(os.Stderr, format, a...) os.Exit(1) } func errToStr(err error) string { if err != nil { return err.Error() } return "" }
可以通过容器方便的启动该服务,然后就可以通过Rest请求,执行Python(Golang)的代码。 -
curl \ -X POST \ http://localhost:8080/run \ -H 'Content-Type: application/json' \ -d '{"language":"python","files":[{"name":"main.py","content":"print(42)"}]}' {"stdout":"42\n","stderr":"","error":""}
在这个项目中,我用了echo来实现一个轻量级的Web服务,而没有使用Glot自带的基于Ruby的服务,这样做的好处是技术栈的统一,因为echo和glot的核心都是用的Golang。
- Code Runner Web https://github.com/gangtao/code_runner_web
有了服务,下面就是前端的UI了。
UI使用了codemirror来做编辑器。Dockerfile如下: FROM naughtytao/code_runner MAINTAINER gangtao@outlook.com Add ./static /home/glot/static
运行Code Runner Web后,就可以在以下的web界面中输入你想要运行的结果,并实时的显示想要运行的结果了。

好了,剩下的还有一些事情要做,就是准备你的演示代码,大家可以参考这个项目,这里缺省是将所有的代码片段放在code/python/ 目录下。运行:http://localhost:8080/#2 就会加载第二个代码片段。
这个是嵌入后的效果:

好了,是不是很Coooooool呢?
另外利用容器来构建应用真的非常非常方便。