golang 实现 gitbook 自刷新

概述

gitbook 是安装在本地的一个服务,它可以将本地的 md 文件组织,构建一系列的 html 文件,索引并可以直接在浏览器访问,一般是 4000 端口。 本文分享一个使用 golang 语言写的小工具:用来帮助本地的 gitbook 监测文件变化,重新构建电子书。做这件事有两个动机:

  • 1.目前使用的 gitbook 版本信息如下:CLI version: 2.3.2,GitBook version: 3.2.3。 nodejs 版本是v8.9.3 本地的 gitbook 运行良好,只是有一个问题:当 md 文件发生变动后,gitbook watch 到了变动,但是处理失败,自动退出:git serve can't restart when file changes #1379 。然而按这个文章中的解决办法:使用 gulp 脚本,写入一段逻辑协助 gitbook 刷新。一番尝试未果,因为 gulp 依赖的其它模块安装失败。
  • 2.这是一个借 golang 练手的机会。 所以, let's get start!

原理

逻辑比较简单:

  • 首先,我们需要有一个模块用于监控书籍变化。这个好办,我们可以对比前后两次统计书籍目录的统计数据,可以很容易知道是否发生了变化。我们简单的实现为:统计每个文件的字节数,使用一个 map 关联起来。
  • 其次,需要结合 gitbook 命令,完成重新构建和重启服务。 md 书籍的命令是 gitbook build。开启服务命令是 gitbook serve。然而我们缺少一个停止命令。这里需要研究一下了。 gitbook 由 npm 完成安装,由 nodejs 去启动。一般我们可以在类似这个路径下找到 gitbook.cmd: C:\Users\edgarlli.WEBANK\AppData\Roaming\npm。gitbook.cmd 描述了 nodejs 去启动 gitbook.js。一个比较粗鲁的办法是,直接关闭 node.exe 这个进程,那么显然 gitbook 服务也将停止。但这个方法可能不太好,原因是,如果有其它服务也依托于 nodejs,则会有多个 node.exe 进程,全部关闭。较有一定的风险。我们可以通过 gitbook 监听 4000 端口,准确找到其对应的进程再进行关闭。

实现

程序接收两个输入:

  • gitbook 书籍目录
  • 要监听的文件的后缀名(一般是 md, jpg, png, bmp 等图片后缀)

将下面代码生成的 exe 置于任意一个目录下,使用命令行:F:\dev\monitor>gitbookMonitor.exe ../blog 可以开启监控服务(实际使用时将 ../blog 换成真正的书籍目录)。

话不多说,上代码

package main
import (
    "path/filepath"
    "os"
    "fmt"
    "flag"
    "os/exec"
    "time"
    "strings"
)

var cmfFileMap map[string]string

func initCmd(){
    var rebuild_restart_cmd = `@echo off
set dir=%1%

if not exist %dir% (
        echo dir not exsits: %dir%
) else (
        echo dir: %dir%
)

call kill_by_port 4000
call kill_by_port 35729

cd %dir%

call gitbook build
call gitbook serve`

    var kill_by_port_cmd = `@echo off
setlocal enabledelayedexpansion

echo param is : %1%

for /f "delims=  tokens=1" %%i in ('netstat -aon ^| findstr %1%') do (
set a=%%i
goto js
)
:js
taskkill /F /pid "!a:~71,5!"`

    cmfFileMap = make(map[string]string)
    cmfFileMap["rebuild_restart.cmd"]=rebuild_restart_cmd;
    cmfFileMap["kill_by_port.cmd"]=kill_by_port_cmd
}

func createCmdFile(dir string) bool{
    for filename,content := range  cmfFileMap{
        var filePath = dir + "\\" + filename;
        stringToFile(filePath, content)
        fmt.Println("finish create file: ", filePath)
        //check file exsit
        cmdOk,_ := PathExists(filePath);
        if(!cmdOk){
            fmt.Println("create cmd file error")
            return false;
        }
    }
    return true;
}


var lastVersion map[string] int64

func getDirStat(path string, ftypes map[string]bool) map[string]int64 {
    var currentVersion map[string]int64 = make(map[string]int64)
    err := filepath.Walk(path, func(path string, f os.FileInfo, err error) error {
        if f == nil {
            return err
        }
        if f.IsDir() {
            return nil
        }
        fileName := f.Name()
        if (0 == strings.Compare(fileName, "rebuild_restart.cmd") ||
                0 == strings.Compare(fileName, "kill_by_port.cmd")){
            return nil
        }
        fileType := fileName[strings.Index(fileName,".")+1 : len(fileName)]
        if(0 != len(ftypes) && ftypes[fileType]){
            currentVersion[path]=f.Size()
        }

        return nil
    })
    if err != nil {
        fmt.Printf("filepath.Walk() returned %v\n", err)
    }
    return currentVersion
}

func monitor(path string, ftypes map[string]bool) bool{
    changed := false
    current := getDirStat(path, ftypes)
    if(len(current) != len(lastVersion)){
        changed = true
    }

    for key,value := range current{
        if(value != lastVersion[key]){
            changed = true
        }

    }
    lastVersion = current
    return changed;
}

func check(e error) {
    if e != nil {
        panic(e)
    }
}

func stringToFile(filename string, str_content string)  {
    fd,_:=os.OpenFile(filename,os.O_RDWR|os.O_CREATE|os.O_TRUNC,0644)

    fd_content:=str_content
    buf:=[]byte(fd_content)
    fd.Write(buf)
    fd.Close()
}

func PathExists(path string) (bool, error) {
    _, err := os.Stat(path)
    if err == nil {
        return true, nil
    }
    if os.IsNotExist(err) {
        return false, nil
    }
    return false, err
}

func gitbookRebuildAndStart(root string){

    //create file
    if(!createCmdFile("./")){
        fmt.Println("create file error")
        return;
    }
    cmdFile,_ := filepath.Abs("rebuild_restart.cmd")
    execCommand(cmdFile,[]string{root})
}

func execCommand(commandName string, params []string) bool {

    cmd := exec.Command(commandName, params...)

    cmd.Start()

    cmd.Wait()
    return true
}

func main(){
    flag.Parse()
    root := flag.Arg(0)
    ftype := flag.Arg(1)
    if(0 == len(root)){
        root = "./"
    }

    if(0 == len(ftype)){
        ftype = "md,jpg,png,bmp"
    }else{
        ftype = ftype + ",md"//保证至少有 md 文件被监视
    }

    ftypes := make(map[string]bool)
    var types []string = strings.Split(ftype,",")
    for _,ct := range types{
        if(0 != len(ct)) {
            ftypes[ct] = true
        }
    }

    fmt.Println("root directory is: ", root, ", fileType: ", ftypes)

    initCmd()
    lastVersion = getDirStat(root, ftypes)

    for {
        if (monitor(root, ftypes)) {
            fmt.Println("detect file changed ")
            gitbookRebuildAndStart(root)
        }

        time.Sleep(5 * time.Second)
    }

}

results matching ""

    No results matching ""