把代码执行演示嵌在你的PPT中

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

“Talk is cheap, show me your code!”

当一个程序员在做技术分享的时候, 代码演示经常是不可或缺的一个环节。然而在你的演示PPT和代码运行之间切换是一件非常恼人事情,而且非常影响演示的节奏和流畅性。要做一个完美的技术分享,能不能把代码的运行嵌入到PPT中呢?

当然可以,我前不久在公司内部分享了一起关于Python的小谜题,大家可以到斗鱼去观看这次分享的视频录播 (很糟糕的是在36分钟后,摄像头偏移了,拍了大半天挂钟)

为了实现代码嵌入ppt,我用到的关键技术包括:

  • Microsoft PowerPoint
  • Glot Code Runner
  • Docker

首先,演示常用的是微软的PowerPoint(至于苹果的Keynote,我暂时还没有找到解决方案),所以你需要安装PowerPoint,在最新的Office套件中,微软提供了一个Webview的插件,有了这个插件,可以直接把一个Web页面,嵌入到Office文档中,自然也就包含了PPT。

193122_2Hoz_1450051.png

注意,为了安全性的考虑,嵌入的web页面必须是https的。

好了有了这个,我们就可以把一个代码运行的web页面直接嵌入到演示文档中,剩下的事情,就是做一个可以运行不同代码的Web应用了。

Glot 是一个开源的在线代码运行平台。其架构如下图所示。

把代码执行演示嵌在你的PPT中_第1张图片

利用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 [email protected]
    
    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 [email protected]
    
    Add ./static /home/glot/static

    运行Code Runner Web后,就可以在以下的web界面中输入你想要运行的结果,并实时的显示想要运行的结果了。
    200528_YVji_1450051.png

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

这个是嵌入后的效果:

好了,是不是很Coooooool呢?

另外利用容器来构建应用真的非常非常方便。

转载于:https://my.oschina.net/taogang/blog/1530258

你可能感兴趣的:(把代码执行演示嵌在你的PPT中)