🛠

Markdownのコードブロックをファイル指定する

2022/03/07に公開

構成

├─main.awk
├─main.rs
├─hello.rs
├─README.txt
└─README.md
main.rs
// start
fn main() {
    println!("Hello, world!");
}
// end
hello.rs
// start
fn main() {
    println!("Hello, Rust!");
}
// end
README.txt
```rust:./main.rs:start:end```

```rust|./main.rs:start:end ./hello.rs:start:end```

実行

$ awk -f main.awk README.txt > README.md
README.md
```rust
fn main() {
    println!("Hello, world!");
}

fn main() {
    println!("Hello, Rust!");
}
```

コード

main.awk
BEGIN {
    # Languages comment-out format
    data["awk"] = "#";
    data["bash"] = "#";
    data["bat"] = "rem";
    data["c"] = "//";
    data["c#"] = "//";
    data["c++"] = "//";
    data["cpp"] = "//";
    data["cs"] = "//";
    data["css"] = "/*";
    data["dart"] = "//";
    data["go"] = "//";
    data["haskell"] = "--";
    data["html"] = "<!--";
    data["java"] = "//";
    data["javascript"] = "//";
    data["jl"] = "#";
    data["js"] = "//";
    data["julia"] = "#";
    data["lisp"] = ";";
    data["lua"] = "--";
    data["makefile"] = "#";
    data["nim"] = "#";
    data["objectivec"] = "//";
    data["php"] = "//";
    data["powershell"] = "#";
    data["py"] = "#";
    data["python"] = "#";
    data["r"] = "#";
    data["rb"] = "#";
    data["rs"] = "//";
    data["ruby"] = "#";
    data["rust"] = "//";
    data["scala"] = "//";
    data["sh"] = "#";
    data["swift"] = "//";
    data["ts"] = "//";
    data["typescript"] = "//";

    # URL encoding
    for (i = 0; i < 256; i++) { 
        c2p[sprintf("%c", i)] = sprintf("%%%02X", i);
    }

    for (i = 48; i < 58; i++) { 
        c2p[sprintf("%c", i)] = sprintf("%c", i);
    }

    for (i = 65; i < 91; i++) {
        c2p[sprintf("%c", i)] = sprintf("%c", i);
    }

    for (i = 97; i < 123; i++) {
        c2p[sprintf("%c", i)] = sprintf("%c", i);
    }

    c2p[" "] = "%20";
    c2p["-"] = "-";
    c2p["."] = ".";
    c2p["_"] = "_";
    c2p["~"] = "~";
    c2p["\""] = "%22";
}


# Counter variable to separate code blocks from the rest of the code
count = 0;

# Variable for playground URL
global_filepath = "";
global_start = "";
global_end = "";
global_comment_word = "";
global_url_word = "";


# Function to retrieve a specific range of source code
function command_runner(path, start, end, comment_word) {
    cmd = "awk /" start "/,/" end "/'{print $0}' " path;
    while (cmd | getline line) {
        if (line != comment_word " " start && line != comment_word " " end) {
            print line;
        }
    }
    close(cmd);
}


# Function to retrieve a specific range of source code for a playground URL
function command_runner_and_playground(path, start, end, comment_word, url_word) {
    cmd = "awk /" start "/,/" end "/'{print $0}' " path;

    print "\n[" url_word "](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&code=";
    while (cmd | getline line) {
        print "%0A";
        for (i = 1; i <= length(line); i++) {
            if (line != comment_word " " start && line != comment_word " " end) {
                print c2p[substr(line, i, 1)];
            }
        }
    }
    close(cmd);
    print ")\n";
}


# Code block generation from program loading
/```.+:.+\..+:.+:.+```/ {
    split($0, code_block, ":");
    
    language = code_block[1];
    filepath = code_block[2];
    start = code_block[3];
    end = code_block[4];

    sub("```", "", language);
    sub("```", "", end);

    print "```" language ":" filepath;
    cmd = "bash -c \"if [[ -e " filepath " ]]; then echo true; else echo false; fi;\"";
    if (cmd | getline line) {
        if (line == "true") {
            # For playground
            if (language == "rust" && length(code_block) == 5) {
                sub("```", "", code_block[5]);
                global_filepath = filepath;
                global_start = start;
                global_end = end;
                global_comment_word = data[language];
                global_url_word = code_block[5];
                count += 1;
            }

            # Stdout source code
            command_runner(filepath, start, end, data[language]);
        } else {
            print "No such file";
        }
    }
    close(cmd);
    print "```";

    count += 1;
}


# Code block operation
/```.+\|(.+\..+:.+:.+?){2,}/ {
    print "";
    split($0, code_block_for_operation, "|");
    
    language = code_block_for_operation[1];
    targets =  code_block_for_operation[2];

    sub("```", "", language);
    split(targets, target_list, " ");

    print "```" language;
    for (i = 1; i <= length(target_list); i++) {
        sub("```", "", target_list[i]);
        split(target_list[i], target, ":");

        filepath = target[1];
        start = target[2];
        end = target[3];

        cmd = "bash -c \"if [[ -e " filepath " ]]; then echo true; else echo false; fi;\"";
        if (cmd | getline line) {
            if (line == "true") {
                command_runner(filepath, start, end, data[language]);
            } else {
                print "ERROR";
            }
        }
        close(cmd);

        if (i != length(target_list)) {
            print "";
        }
    }
    print "```";
}


# Processing of code other than code blocks and for playground
{   
    if (count == 0) {
        print $0;
        count = 0;
    } else if (count == 2) {
        OFS = "";
        ORS = "";
        command_runner_and_playground(\
            global_filepath,
            global_start,
            global_end,
            global_comment_word,
            global_url_word\
        );
        OFS = " ";
        ORS = "\n";
        count = 0;

https://github.com/ogty/article-continuous-integration

Discussion