Gar

作者: 明夷 | 来源:发表于2021-03-09 09:05 被阅读0次

    Gar 是一个 Bash 脚本程序——我写的,基于我的野生的 Bash 编程经验——,用于管理 Markdown 文档项目,可将 Markdown 文档集合其转化为 HTML 文档集合。Gar 的运行,依赖 pandoc,git,tree 以及一个能在 Shell(命令行)里打开指定网页文件的网页浏览器。Gar 默认将 Firefox 作为网页浏览器,但是可在文档项目根目录的 gar.conf 文件中指定其它符合要求的网页浏览器。

    文档项目初始化

    命令:gar init 文档项目名

    例如:

    $ gar init demo
    [master (root-commit) 6f7dd1c] init
     1 file changed, 2 insertions(+)
     create mode 100644 gar.conf
    

    以下命令可观察 gar init 创造了什么:

    $ cd demo
    $ ls -a
    .  ..  gar.conf  .git  .gitignore  images  output  source
    
    $ gar tree
    demo
    demo
    ├── gar.conf
    ├── images
    ├── output
    └── source
    
    $ git log
    commit e2eb30a6f915a8571fe026a76febbe52ac1ab38f (HEAD -> master)
    Author: xxx <xxx@yyy.zzz>
    Date:   Fri Mar 12 14:28:43 2021 +0800
    
        init
    

    文档项目初始化后,文档的撰写和编辑工作主要在 source 目录进行。Gar 将 Markdown 文档转化为 HTML 文档后,放在 output 子目录内。

    文档的插图皆位于 images 目录,且被 Markdown 和 HTML 文档共享,亦即在 Markdown 文档中要使用相对路径插入图片,例如:

    
    ... 上文 ...
    
    在 Markdown 文档中,推荐使用引用式插图语法:
    
    ![test][test]
    
    ... 下文 ... 
    
    [test]: ../../images/my-programs/gar/test.png
    

    文档项目初始化后,可打开文档项目根目录里的配置文件 gar.conf,在其中设定 Gar 默认使用的网页浏览器以及文档作者的名字。例如:

    #!/bin/bash
    BROWSER_FOR_GAR=firefox
    AUTHOR="李磨刀"
    

    截止到目前,gar.conf 没有其他设定。

    文集创建与删除

    进入 source 目录:

    $ cd source
    

    创建文集 foo:

    $ gar new class foo
    

    可使用 gar tree 查看文档项目的目录变化,观察 gar new class 命令创造了什么:

    $ gar tree
    demo
    ├── gar.conf
    ├── images
    │   └── foo
    ├── output
    │   └── foo
    └── source
        └── foo
    

    可一次创建多个文集:

    $ gar new class a b c
    

    结果为:

    $ gar tree
    demo
    ├── gar.conf
    ├── images
    │   ├── a
    │   ├── b
    │   ├── c
    │   └── foo
    ├── output
    │   ├── a
    │   ├── b
    │   └── c
    └── source
        ├── a
        ├── b
        ├── c
        └── foo
    

    删除文集:

    $ gar remove class a b c
    $ gar tree
    demo
    ├── gar.conf
    ├── images
    │   └── foo
    ├── output
    │   └── foo
    └── source
        └── foo
    

    可在文集里创建子文集:

    $ cd foo
    $ gar new-class a
    $ gar tree
    demo
    ├── gar.conf
    ├── images
    │   └── foo
    │       └── a
    ├── output
    │   └── foo
    │       └── a
    └── source
        └── foo
            └── a
    

    可创建嵌套文集:

    $ gar new class b/c/d/e/f
    $ gar tree
    demo
    ├── gar.conf
    ├── images
    │   └── foo
    │       ├── a
    │       └── b
    │           └── c
    │               └── d
    │                   └── e
    │                       └── f
    ├── output
    │   └── foo
    │       ├── a
    │       └── b
    │           └── c
    │               └── d
    │                   └── e
    │                       └── f
    └── source
        └── foo
            ├── a
            └── b
                └── c
                    └── d
                        └── e
                            └── f
    

    将上述试验复盘:

    $ gar remove class a b
    $ gar tree
    demo
    ├── gar.conf
    ├── images
    │   └── foo
    ├── output
    │   └── foo
    └── source
        └── foo
    

    提示,目前工作目录仍为 source/foo。

    创建和删除文档

    在文集目录内,使用 gar new post 创建内容为空的文档。例如,在 source/foo 内创建 test.md 文档:

    $ gar new post test.md
    [master 6a894eb] Added test.md
     1 file changed, 0 insertions(+), 0 deletions(-)
     create mode 100644 source/foo/test.md
    

    test.md 内容如下:

    ---
    title: 
    author: 李磨刀
    date: 2021 年 03 月 12 日
    ...
    

    这是 pandoc 能够支持的 YAML 格式的文件头。title 的值,需要手工设定,毕竟 Gar 没法知道我要写一份什么文章。

    查看一下项目的目录发生了哪些变动:

    $ gar tree
    demo
    ├── gar.conf
    ├── images
    │   └── foo
    │       └── test
    ├── output
    │   └── foo
    └── source
        └── foo
            └── test.md
    
    $ git log
    commit 91ea8d1599269ad4fdb4aae15b73d5e4cbd7a4ad (HEAD -> master)
    Author: xxx <xxx@yyy.zzz>
    Date:   Fri Mar 12 14:49:41 2021 +0800
    
        Added test.md
    
    commit e2eb30a6f915a8571fe026a76febbe52ac1ab38f (HEAD -> master)
    Author: xxx <xxx@yyy.zzz>
    Date:   Fri Mar 12 14:28:43 2021 +0800
    
        init
    

    每次创建文档时,Gar 会调用 git 记录文档创建历史。

    可一次创建多份内容为空的文档:

    $ gar new post a.md b.md c.md
    [master 25e7d65] Added a.md b.md c.md
     3 files changed, 0 insertions(+), 0 deletions(-)
     create mode 100644 source/foo/a.md
     create mode 100644 source/foo/b.md
     create mode 100644 source/foo/c.md
    
    $ gar tree
    demo
    ├── gar.conf
    ├── images
    │   └── foo
    │       ├── a
    │       ├── b
    │       ├── c
    │       └── test
    ├── output
    │   └── foo
    └── source
        └── foo
            ├── a.md
            ├── b.md
            ├── c.md
            └── test.md
    

    使用 gar remove post 可删除当前工作目录下的文档。以下命令可将上述创建的文档一举删除:

    $ gar remove post test.md a.md b.md c.md
    [master 3684217] Remove test.md a.md b.md c.md
     4 files changed, 24 deletions(-)
     delete mode 100644 source/foo/a.md
     delete mode 100644 source/foo/b.md
     delete mode 100644 source/foo/c.md
     delete mode 100644 source/foo/test.md
    

    每次删除文档,git 会记录文档的删除历史。

    经过上述操作后,这个试验性的文档项目又复盘为:

    $ gar tree
    demo
    ├── gar.conf
    ├── images
    │   └── foo
    ├── output
    │   └── foo
    └── source
        └── foo
    

    网页的生成和预览

    记住,当前的工作目录依然是 source/foo。下面的命令重新创建 test.md:

    $ gar new post test.md
    

    然后用文本编辑器打开 test.md,将其内容修改为:

    ---
    title: Hello Gar!
    author: 李磨刀
    date: 2021 年 03 月 12 日
    ...
    
    这只是一份无用的的示例文档。
    

    使用 gar convert 命令可将文档 test.md 转换为网页文件 test.html:

    $ gar convert test.md
    

    查看文档项目发生的变化:

    $ gar tree
    demo
    ├── gar.conf
    ├── images
    │   └── foo
    │       └── test
    ├── output
    │   └── foo
    │       └── test.html
    └── source
        └── foo
            └── test.md
    

    倘若当前文集内有多份文档,也可以一次性将其转换为一组网页文件,例如:

    $ gar convert test.md a.md b.md c.md
    

    使用 gar preview 命令,可将文档转化为网页文件,并由 Gar 默认的网页浏览器打开:

    $ gar preview test.md
    
    test.html

    gar preview 不支持多份文档一次性转换和预览。

    附录

    Gar 的全部代码:

    #!/usr/bin/env bash
    
    SCRIPT_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
    GAR_CONF=gar.conf
    GAR_CSS=gar.css
    SOURCE=source
    IMAGES=images
    OUTPUT=output
    GAR_ALL=("$SOURCE" "$IMAGES" "$OUTPUT")
    
    function error_msg {
        echo "$1"; exit -1
    }
    
    function check_argument {
        if [ -z "$1" ]; then error_msg "$2"; fi
    }
    
    function gar_commit {
        gar_goto_root
        git add .
        git commit -a -m "$1"
    }
    
    function gar_goto_root {
        if [ "$(pwd)" = "/" ]
        then
            erroe_msg "gar.conf Not found!"
        elif [ -e "$GAR_CONF" ]
        then
            return 0
        else
            cd ../
            gar_goto_root
        fi
    }
    
    function gar_is_not_workspace {
        local current_path="$(pwd)"
        gar_goto_root
        local root_path="$(pwd)"
        local relative_path=$(realpath "$current_path" \
                                       --relative-to="$(pwd)")
        if [ "${relative_path#"$SOURCE"}" = "$relative_path" ]
        then
            echo "true"
        else
            echo "false"
        fi
    }
    
    function gar_shortcut {
        if [ -z "$1" ]
        then
            local current_path="$(pwd)"
        else
            local current_path="$1"
        fi
        gar_goto_root
        local short_path=$(realpath "$current_path" \
                                    --relative-to="$(pwd)/$SOURCE")
        if [ "$short_path" = "." ]
        then
            echo ""
        else
            echo "$short_path"
        fi
    }
    
    # 给文件添加 pandoc 支持的 YAML metadata
    function gar_init_post {
        local MARK="$(pwd)"
        gar_goto_root
        source "$GAR_CONF"
        cd "$MARK"
        local DATE="$(date +"%Y 年 %m 月 %d 日")"
        echo -e "---\ntitle: \nauthor: $AUTHOR\ndate: $DATE\n...\n" > "$1"
    }
    
    function gar_markdown_to_html {
        CURRENT_PATH="$(pwd)"
        gar_goto_root
        local css_path="$(realpath "./" --relative-to="$OUTPUT/$1")"
        pandoc "$SOURCE/$1/$2" -s --mathjax \
               -c "$css_path/$GAR_CSS" --highlight-style pygments \
               -o "$OUTPUT/$1/${2%.*}.html"
        cd "$CURRENT_PATH"
    }
    
    function gar_init {
        check_argument "$1" "You should tell me the name of the project!"
        mkdir "$1"
        cd "$1"
        echo "BROWSER_FOR_GAR=firefox" > $GAR_CONF
        mkdir $SOURCE $OUTPUT $IMAGES
        cat "$SCRIPT_PATH/gar.css" > $GAR_CSS
        git init -q
        touch .gitignore
        for i in ".gitignore" "gar.css" "$OUTPUT" "$IMAGES"
        do
            echo "$i" >> .gitignore
        done
        gar_commit "init"
    }
    
    function gar_new {
        if [ "$(gar_is_not_workspace)" = "true" ]
        then
            error_msg "This is not workspace!"
        fi
        case $1 in
            class)
                check_argument "$2" "Tell me the name of the class!"
                for i in "${@:2}"
                do
                    local CURRENT_PATH="$(pwd)"
                    local CLASS="$(gar_shortcut "$CURRENT_PATH")"
                    for j in "${GAR_ALL[@]}"
                    do
                        gar_goto_root
                        cd "$j/$CLASS" && mkdir -p "$i"
                    done
                    cd "$CURRENT_PATH"
                done
            ;;
            post)
                check_argument "$2" "Tell me the name of the post!"
                for i in "${@:2}"
                do
                    gar_init_post "$i"
                    local CURRENT_PATH="$(pwd)"
                    local POST="$(gar_shortcut "$CURRENT_PATH/$i")"
                    gar_goto_root
                    mkdir -p "$IMAGES/${POST%.*}"
                    cd "$CURRENT_PATH"
                done
                gar_commit "Added ${*:2}"
                ;;
            *)
                error_msg "I do not understand you!"
                ;;
        esac
    }
    
    function gar_remove {
        if [ "$(gar_is_not_workspace)" = "true" ]
        then
            error_msg "This is not workspace!"
        fi
        local CURRENT_PATH="$(pwd)"
        case $1 in
            class)
                check_argument "$2" "Tell me the name of the class!"
                for i in "${@:2}"
                do
                    local CLASS="$(gar_shortcut "$CURRENT_PATH/$i")"
                    for j in "${GAR_ALL[@]}"
                    do
                        gar_goto_root
                        cd "$j" && rm -rf "$CLASS"
                    done
                    cd "$CURRENT_PATH"
                done
                gar_commit "Remove ${*:2}"
            ;;
            post)
                check_argument "$2" "Tell me the name of the post!"
                for i in "${@:2}"
                do
                    rm -f "$i"
                    local POST="$(gar_shortcut "$CURRENT_PATH/$i")"
                    gar_goto_root
                    rm -rf "$IMAGES/${POST%.*}"
                    rm -f "$OUTPUT/${POST%.*}.html"
                    cd $CURRENT_PATH
                done
                gar_commit "Remove ${*:2}"
                ;;
            *)
                error_msg "I do not understand you!"
                ;;
        esac
    }
    
    function gar_rename {
        if [ "$(gar_is_not_workspace)" = "true" ]
        then
            error_msg "This is not workspace!"
        fi
        local CURRENT_PATH="$(pwd)"
        case $1 in
            class)
                check_argument "$2" "Tell me the name of the class!"
                if [ ! -d "$2" ]
                then
                    error_msg "The class not found!"
                fi
                check_argument "$3" "Tell me the new name of the class!"
                local CLASS="$(gar_shortcut "$CURRENT_PATH")"
                for i in "${GAR_ALL[@]}"
                do
                    gar_goto_root
                    cd "$i/$CLASS" && mv "$2" "$3"
                done
                gar_commit "$2 -> $3"
                ;;
            post)
                check_argument "$2" "Tell me the name of the post!"
                if [ ! -e "$2" ]
                then
                    error_msg "The post not found!"
                fi
                check_argument "$3" "Tell me the new name of the post!"
                mv "$2" "$3"
    
                local CLASS="$(dirname "$(gar_shortcut "$(pwd)/$2")")"
                gar_goto_root && cd "$IMAGES/$CLASS" && mv "${2%.*}" "${3%.*}"
                gar_goto_root && cd "$OUTPUT/$CLASS"
                if [ -e "${2%.*}.html" ]
                then
                    mv "${2%.*}.html" "${3%.*}.html"
                fi
                gar_commit "$2 -> $3"
                ;;
            *)
                error_msg "I do not understand you!"
                ;;
        esac
    }
    
    function gar_convert {
        if [ "$(gar_is_not_workspace)" = "true" ]
        then
            error_msg "This is not workspace!"
        fi
        check_argument "$1" "Tell me the name of the post!"
        for i in "${@:1}"
        do
            local CLASS="$(gar_shortcut "$CURRENT_PATH")"
            gar_markdown_to_html "$CLASS" "$i"
        done
        gar_commit "Modified ${*:2}"
    }
    
    function gar_preview {
        if [ "$(gar_is_not_workspace)" = "true" ]
        then
            error_msg "This is not workspace!"
        fi
        check_argument "$1" "You should tell me the name of the post!"
        local CLASS="$(dirname "$(gar_shortcut "$(pwd)/$1")")"
        gar_markdown_to_html "$CLASS" "$1"
    
        gar_goto_root && source $GAR_CONF
        $BROWSER_FOR_GAR "$OUTPUT/$CLASS/${1%.*}.html"
    }
    
    # 选项:
    case $1 in
        init) gar_init "$2" ;;
        new)  gar_new "${@:2}" ;;
        remove) gar_remove "${@:2}" ;;
        rename) gar_rename "${@:2}" ;;
        convert) gar_convert "${@:2}" ;;
        preview) gar_preview "$2" ;;
        tree)
            gar_goto_root
            GAR_ROOT="$(basename "$(pwd)")"
            case $2 in
                source) tree "$SOURCE" ;;
                output) tree "$OUTPUT" ;;
                images) tree "$IMAGES" ;;
                *) cd .. && tree "$GAR_ROOT" ;;
            esac
            ;;
        *)
          error_msg "I do not understand you!"
          ;;
    esac
    

    Gar 在使用 pandoc 将 Markdown 文档转化为网页时,需要一个 CSS 文件 gar.css,其内容如下:

    html {
        font-size: 16px;
        line-height: 1.8rem;
    }
    
    body {
        margin: 0 auto;
        max-width: 50rem;
        padding: 50px;
        hyphens: auto;
        word-wrap: break-word;
        font-kerning: normal;
    }
    
    header {
        text-align: center;
        margin-bottom: 4rem;
    }
    
    h1, h2, h3, h4, h5 {
        margin-top: 2rem;
        margin-bottom: 2rem;
        color: #d35400;
    }
    
    h1.title { font-size: 2.3rem; }
    h1 { font-size: 1.8rem; }
    h2 { font-size: 1.65rem; }
    h3 { font-size: 1.5em; }
    h4 { font-size: 1.35rem; }
    h5 { font-size: 1.2rem; }
    
    p {
        margin: 1.3rem 0;
        text-align: justify;
    }
    
    figure {
        text-align: center;
    }
    figure img {
        width: 80%;
    }
    figure figcaption {
        font-size: 0.9rem;
    }
    
    pre {
        padding: 1rem;
        font-size: 0.9rem;
        line-height: 1.6em;
        overflow:auto;
        background: #f8f8f8;
        border: 1px solid #ccc;
        border-radius: 0.25rem;
    }
    
    code {
        color: #e83e8c;
    }
    pre code {
        color: #333366;
    }
    
    /* metadata */
    p.author, p.date { text-align: center; margin: 0 auto;}
    
    /* 文章里小节标题的序号与标题名称之间的间距 */
    span.section-sep { margin-left: 0.5rem; margin-right: 0.5rem; }
    
    
    blockquote {
        margin: 0px !important;
        border-left: 4px solid #009A61;
    }
    
    blockquote p {
        font-size: 1rem;
        line-height: 1.8rem;
        margin: 0px !important;
        text-align: justify;
        padding:0.5em;
    }
    

    上述 gar.css 并无特别之处,完全可根据自己对 css 的熟悉程度并结合需要自行定制,但是要记得将它放在 gar 脚本同一目录下。

    相关文章

      网友评论

          本文标题:Gar

          本文链接:https://www.haomeiwen.com/subject/vsevqltx.html