Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

mdbook-lang

mdbook-lang是一个mdbook预处理器插件和多编程语言playground服务器,支持在浏览器中通过与playground交互、运行mdbook电子书嵌入的多种编程语言代码,并展示结果。而该playground服务器可以自行部署或本地部署,可以容易扩展到其他编程语言。

本软件受mdbookmdbook-repl启发,但mdbook基于https://play.rust-lang.org实现的playground,目前仅支持rust语言;而mdbook-repl主要支持pythonjavascripttypescript等解释型语言,两者都依赖在线playground服务器,使得mdbook支持的编程语言不易扩展。而本软件借助自主部署的编译器环境为mdbook电子书嵌入的多编程语言代码段架起浏览器和编译器之间的桥梁,且便于扩展,也给出了多电子书和沙箱安全等配置。

如下电子书中的C/C++代码,在安装了mdbook-lang主机环境中,是否让其提供支持的区别,使用mdbook-lang支持:

#include <iostream>
using namespace std;
int main() {
    cout << "Hello, World!" << endl;
    return 0;
}

不使用mdbook-lang支持:

#include <iostream>
using namespace std;
int main() {
    cout << "Hello, World!" << endl;
    return 0;
}

在启用mdbook-lang支持的代码中,以ACE Editor作为代码编辑器,可对其进行可配置的编辑、重置和运行等。您可以直接在浏览器中修改并执行代码,实时查看输出结果。如果您是教师为学生布置练习,还可以控制剪切、复制和粘贴等操作,并禁用浏览器调试功能,以实现更受控的教学环境。

本软件仍处于持续开发和优化中,未来将支持更多的编程语言。

用法

本工具是一个mdbook预处理插件和可独立运行的playground编译和运行服务器。

安装

有两种方式可以安装 mdbook-lang

  • 如果你已经有 Rust 环境,可以通过cargo工具安装mdbook-lang
cargo install mdbook-lang
  • 或者你可以从 github 页面下载二进制文件,并将该二进制文件最终存放路径加入到用户或全局 PATH环境变量中。

你可以通过以下命令检查安装情况:

mdbook-lang --version

配置

安装完成后,通过 mdbook-lang install 命令自动在书籍的 book.toml 文件中配置 mdbook-lang 插件,使得现有基于mdbook工具的电子书启用该插件,该默认配置根据需要可进行修改。

$ mdbook-lang install /path/to/your/book

该语句将设置 mdbook-lang 预处理插件和编译服务器的 url 参数。

例如:

[book]
authors = ["gaoxu.jeffrey"]
language = "en"
multilingual = false
src = "src"
title = "mdbook-lang example"

[preprocessor]

[preprocessor.lang]
command = "mdbook-lang"
server = "http://127.0.0.1:3333/api/v1/build-code"
cpp-enable = true
java-enable = true
go-enable = true
python-enable = true
javascript-enable = true
typescript-enable = true
scheme-enable = true
editable = true
disable-devtool-auto = false
disable-menu = false
clear-log = false
disable-select = false
disable-copy = false
disable-cut = false
disable-paste = false
ace-strict = false

[output]

[output.html]
additional-js = ["jquery.js", "disable-devtool.js", "lang.js"]
additional-css = ["lang.css"]
  • server: 编译服务器的 url

现已部署了一个编译服务器,你可以直接修改 server 的值进行测试。

server = "https://183.205.132.14:3000/playground/api/v1/build-code"

注意 你需要先在浏览器中打开 https://183.205.132.14:3000 并忽略或关闭安全警告,否则在电子书中运行编程语言代码时无法访问该服务。

  • language-enable: 启用某种语言,默认值为 true

  • disable-devtool-auto: 禁用浏览器调试器功能,默认值为 false,即不禁用。

  • ace-strict: 启用 ACE编辑器的严格模式,默认值为 false。启用后,ACE 编辑器将不允许电子书读者在玩转代码时在ACE编辑环境中剪切、复制和粘贴代码等功能。

运行编译服务器

仅 Windows 7/8/10/11 需要安装/卸载服务

所有命令都以 管理员 身份运行。

  • 安装 mdbook-lang 编译、运行服务器软件为Windows操作系统中的一个服务:
C:\Windows\System32>mdbook-lang server install --hostname 127.0.0.1 --port 3333

或者当install无参数时候,采用默认参数: hostname127.0.0.1port3333

C:\Windows\System32>mdbook-lang server install

当不再需要 mdbook-lang 服务或想更改 hostname 和/或 port 时,应先 以子server的子命令命令uninstall卸载服务,再以不同参数通过install重新安装服务 :

永久删除服务:

mdbook-lang uninstall

如果你想更改 hostname 和/或 port

mdbook-lang server uninstall
mdbook-lang server install --hostname 0.0.0.0

类 Unix 和 Windows 7/8/10/11操作系统

启动 mdbook-lang 编译服务器

用以下命令启动编译服务器:

mdbook-lang server start

或者在类 Unix 系统中使用 --hostname 或简写 -n--port 或简写 -p 参数启动编译服务器:

mdbook-lang server start --hostname 127.0.0.1 -port 3333

对于 Windows,需要具有管理员权限。

  • 启动服务
C:\Windows\System32>mdbook-lang server start

或者通过操作系统提供的 服务管理器任务管理器 图形界面工具启动。

stop/restart/status 子命令

  • 停止服务器
mdbook-lang server stop
  • 重启服务器
mdbook-lang server restart
  • 检查服务器状态
mdbook-lang server status

运行 mdbook,并打开默认浏览器,阅读电子书,playing内嵌代码段:

mdbook serve -o

选项

mdbook-lang所支持的语言的markdown代码块中,可使用扩展的选项,完成定制功能:

norun

norun 选项会使代码块不被mdbook-lang预处理器渲染。如果你想展示一些不应被执行的代码示例,并且 language-enable=true 时,可以使用该选项。


```java,norun
// java codeblock with norun option
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, world!");
    }
}
```

这样该代码块不会被预处理器渲染:

// java codeblock with norun option
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, world!");
    }
}

```java
// java codeblock without norun option
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, world!");
    }
}
```

java-enable=true,且无nolang选项时该代码块会被预处理器渲染:

// java codeblock with norun option
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, world!");
    }
}

快捷键

操作系统快捷键说明
WindowsCtrl-Enter运行代码
MacCommand-Enter运行代码
WindowsCtrl-Shift-Enter清除代码编译或运行结果
MacCommand-Shift-Enter清除代码编译或运行结果

语言扩展

该预处理器只识别特定语言的特定扩展。例如,C++ 代码只能用 c++ 或 cpp 代码块,Python 代码只能用 python 或 py 代码块。

完整扩展名列表如下:

语言扩展名编译器
C++cpp, c++, cclang++
Javajavasun jdk/openjdk
Gogogolang
Pythonpy, pythonpython2, python3(python 目录需在 PATH 环境变量中)
JavaScriptjs, javascriptnode.js
TypeScriptts, typescriptnode.js, tsc
Schemelisp, schemegambit-scheme(gsi 目录需在 PATH 环境变量中)

如需支持某些语言,你需要在编译服务器主机上安装相应的编译器。

性能

通过浏览器远程执行代码块的速度非常快,仅受网络延迟和编译服务器的负载影响。

A高级用法

通过nginx反向代理部署多本电子书

你可以在同一台主机上部署多本书籍,只需一个具有一个公网IP地址和一个端口号的物理服务器、编译服务器mdbook-lang和 nginx。

安装 nginx

不同平台的安装方式有所不同,具体参考install nginx

ubuntu

sudo apt-get install nginx

centos

sudo yum install nginx

macos

brew install nginx

windows

choco install nginx

configure nginx

sudo vim /etc/nginx/conf.d/mdbook-lang.conf

and add the following content:

# /etc/nginx/conf.d/mdbook.conf
map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
}

server {
    # nginx listen port
    listen 3000;
    server_name 0.0.0.0;

    # compiler server
    location /playground/{
    	proxy_pass http://127.0.0.1:3333/;
    }
    # java object oriented programming mdbook
    location /joop/{
        proxy_pass http://127.0.0.1:2000/;
    }
    location /joop/__livereload{
        proxy_pass http://127.0.0.1:2000/__livereload/;
    }

    # rust-course mdbook
    location /rust-course/{
        proxy_pass http://127.0.0.1:2001/;
    }
    location /rust-course/__livereload{
        proxy_pass http://127.0.0.1:2001/__livereload/;
    }
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection 'upgrade';
    proxy_set_header Host $host;
    proxy_cache_bypass $http_upgrade;
}

启动 nginx

sudo nginx -s reload

启动编译服务器

  • 类Unix操作系统:Linux/MacOS/FreeBSD/NetBSD等
mdbook-lang server --hostname 127.0.0.1 --port 3333
  • Windows7/8/10/11操作系统
mdbook-lang server start

如果在windows系统中提示没有安装服务,则执行如下命令,先将编译服务器安装为系统服务:

mbook-lang server install

启动电子书

$ cd /path/to/joop/
$ mdbook serve start --hostname 127.0.0.1  --port 2000 > joop-mdbook.log 2> &1 &
$ cd /path/to/rust-course/
mdbook serve start --hostname 127.0.0.1 --port 2001 > rust-course.log > 2&1 &

如果电子书没有安装mdbook-lnag支持,则需要在包含有book.toml的电子书根目录中执行命令:

mdbook-lang install

修改book.toml中的编译服务器配置

server = "https://183.205.132.14:3000/playground/api/v1/build-code"

通过浏览器访问电子书

沙箱安全

安装沙箱工具:firejail

ubuntu

sudo apt-get install firejail

centos

sudo yum install firejail

macos

brew install firejail

配置沙箱工具:firejail

在合适位置新建沙箱配置文件:

sudo vim /etc/firejail/mdbook-lang-server.profile

文件内容如下

# /etc/firejail/mdbook-lang-server.profile
include disable-common.inc
# include disable-exec.inc
# noexec ${HOME}
noexec ${RUNUSER}
noexec /dev/shm
noexec /var

# must be canceled for c/c++ execute output.exe in /tmp
# noexec /tmp

include disable-passwdmgr.inc
include disable-programs.inc
quiet

net none

nodbus
nodvd
nogroups
nonewprivs
noroot
nosound
notv
nou2f
novideo
protocol inet,inet6
seccomp
shell none
# tracelog

disable-mnt
private
# for debug porpose
# private-bin ls

# allow c/c++ tools chain
private-bin mdbook-lang
private-bin clang++
private-bin ld

# allow java tools chain
private-bin java
private-bin javac

# allow python tools chain
private-bin python2
private-bin python3
private-bin python

# allow go tools chain
private-bin go

# allow javascript/typescript tools chain
private-bin node
private-bin tsc

# allow lisp/scheme tools chain
private-bin scheme-r5rs

# allow c/c++ output executable
private-bin output.exe

# must be canceled for java/javac
# private-lib

blacklist /opt
blacklist /etc/nginx
blacklist /etc/firejail
blacklist /etc

blacklist /sbin
# checked for java,c/c++,
blacklist /bin
# for python interpreter
# blacklist /usr/bin
blacklist /usr/sbin
blacklist /usr/libexec
blacklist /usr/local/sbin
blacklist /usr/local/lib
blacklist /usr/local/libexec
blacklist /usr/libexec/firejail
blacklist /usr/libexec/firejail/firejail-config
blacklist /usr/libexec/firejail/firejail-profile
blacklist /usr/libexec/firejail/firejail-shell
blacklist /usr/libexec/firejail/firejail-shell-wrapper
blacklist /usr/libexec/firejail/firejail

configure envs

sudo vim ~/.bashrc

and add the following content:

export MDBOOKLANG_SERVER_SANDBOX_CMD="firejail"
export MDBOOKLANG_SERVER_SANDBOX_ARGS="--profile=/etc/firejail/mdbook-lang-server.profile:--quiet"

Note: sandbox is for single or multiple books, local or remote server is ok.

Enjoy it!

沙箱配置

添加白名单

如果所部署的python编程环境不在系统路径,而是在${HOME}目录,除了将其添加到$HOME/.bashPATH环境变量之外,需要使用命令安装相应包:

  • 采用系统python环境,用非超管账户安装包,则额外包安装路径为:${HOME}/.local/lib/python3.10/site-packages
$ python -m pip install numpy

则需要在firejail配置尾部添加语句,注意python版本号:

whitelist ${HOME}/.local/lib/python3.10/site-packages

添加编译器白名单

如果安装了其他编译器,需要在firejail配置文件中添加:

private-bin your-compiler-file-name

C/C++

C/C++ 是一种系统编程语言,能编写在几乎任何平台上运行的程序。它是一种功能强大的编程语言,常用于开发底层程序,如操作系统、驱动程序和游戏。同时,它也是学习编程的优秀选择。

#include <iostream>
using namespace std;
int main() {
    cout << "Hello, World!" << endl;
    return 0;
}

Go

Go 是一种静态类型、编译型编程语言,由 Google 的 Robert Griesemer、Rob Pike 和 Ken Thompson 设计。它在语法上与 C 类似,但具有内存安全、垃圾回收、结构化类型和基于 CSP 的并发特性。

Go 常用于系统编程、网络编程和分布式系统开发,也被广泛应用于构建 Web 应用、微服务和云原生应用。

package main
import "fmt"
func main() {
	fmt.Println("Hello, World!")
}

Java

Java 是一种多范式编程语言,支持面向对象、命令式和函数式等编程风格。它是一种编译型语言,其源代码会被编译为字节码,可以在任何 Java 虚拟机(JVM)上运行,而不受底层计算机架构的影响。

Java 被广泛用于开发各种应用,包括桌面应用、Web 应用、移动应用和企业级应用。它也常用于开发大规模分布式系统(如互联网应用)以及实时系统(如视频游戏)。

编译器/运行服务器使用 javac[.exe] 编译器编译java源代码代码为字节码,通过 java[.exe] 解释器解释运行字节码。编译器/运行服务器会查找 public class 以确定不要保存的正确文件名,并查找 main classmain 方法来运行程序。

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

你不能在同一个文件中编写多个 public class,否则编译器会报错。例如,下面的代码会导致错误:

public class HelloWorld {
    public static void main(String[] args) {
        new Fibonacci().run(10);
    }
}

public class Fibonacci {
    public void run(int n) {
        int n = 10;
        int a = 0, b = 1;
        for (int i = 0; i < n; i++) {
            System.out.println(a);
            int temp = a;
            a = b;
            b = temp + b;
        }
    }
}
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

public class Sum {
     public static void main(String[] args) {
        int sum = 0;
        for (int i = 0; i <= 100; i++) {
            sum += i;
        }
        System.out.println("Sum of first 100 numbers is: " + sum);
    }
}

另外,编译器或运行服务器会查找包含 main 方法的主类(main class)来运行程序。如果存在多个主类,编译器或运行服务器通常会按照类名的字母表顺序选择第一个找到的主类来执行。

class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

class Sum {
     public static void main(String[] args) {
        int sum = 0;
        for (int i = 0; i <= 100; i++) {
            sum += i;
        }
        System.out.println("Sum of first 100 numbers is: " + sum);
    }
}

The output of the above code will be:

Hello, World!

此处的main方法,实际函数头部声明应为:public static void main(String[] args)。包含了主方法的类称为是主类。如下几个代码不包含主方法、所在类也不是主类

class HelloWorld {
    public void main(String[] args) {
        System.out.println("Hello, World!");
    }
}
class Sum {
    public static int main(String[] args) {
        int n = 100;
        int sum = 0;
        for (int i = 0; i <= n; i++) {
            sum += i;
        }
        System.out.println("Sum of first " + n + " numbers is: " + sum);
    }
}
class Fib {
    public static void main(String args) {
        int fib[] = new int[30];
        fib[0] = fib[1] = 1;

        for (int i = 2; i < 30; i++) {
            fib[i] = fib[i - 1] + fib[i - 2];
        }

        for (int i = 0; i < 30; i++) {
            System.out.println(fib[i]);
        }

    }
}
class Mul {
    public static long main() {
        long n = 30;
        long  m = 1;
        for (int i = 1; i <= n; i++) {
            m *= i;
        }
        return m;
    }
}

Python

Python 是一种高级的、解释型的编程语言,广泛应用于 Web 开发、数据分析、人工智能、机器人、嵌入式和自动化等领域。它以简洁、易读和多功能著称。

print("Hello, World!")

Javascript

JavaScript 是一种高级的、解释型的动态编程语言,主要用于创建交互式网页。它是一种多功能语言,可用于前端和后端开发。

console.log("Hello, World!");

Typescript

TypeScript 是一种强类型、面向对象的编程语言,设计用于将改语言编译为 JavaScript。它是 JavaScript 的超集,这意味着任何有效的 JavaScript 代码也是有效的 TypeScript 代码。

TypeScript 为 JavaScript 增加了静态类型,这意味着变量必须声明为特定的类型。这有助于在编译时而不是运行时捕获错误。

let person: { name: string; age: number; isStudent: boolean } = {

    name: "John",
    age: 30,
    isStudent: true,
}
console.log(person);

Scheme

Scheme 是一种易于学习和使用的编程语言。它属于 Lisp 语言家族,是 Lisp 的一种方言。Scheme 是一门函数式编程语言,这意味着它通过函数来进行计算。

;scheme,lisp
(define (greet-world)
  (let ((southern-germany "Grüß Gott!")
        (chinese "世界,你好")
        (english "World, hello"))
    (let ((regions (list southern-germany chinese english)))
      (for-each (lambda (region)
                  (display region)
                  (newline))
                regions))))
(greet-world)