k3kaimu
6/2/2013 - 12:16 PM

//##$ dmc %src% //## %src.exe% みたいに使う。

//##$ dmc %src% //## %src.exe% みたいに使う。

/** ソースファイルの先頭に書かれた"//##"行を読み取って、コマンドを打ち込みます。
 *  "//##"行は複数でも構いません。複数行の場合には、上から順番に実行されます。
        //##$ <commands>                    このようにすると、shellでコマンドを打ち込みます。
                                            commandsの中に"%identifier%"があれば、展開されます。
                                            commandsはスペース区切りで入力する必要があります。
        
        //##& set waitTime <long>            プロセスを待つ最大の時間を設定します。

        //##& let <identifier> = <string>   <identifier>を<string>に束縛します。
                                            <string>には%src%や%arg[n]%など%<identifier>%を入れることもでき、それらは展開されます。

        //## <string>                       現在のプロセスの標準入力に<string>を入れ込みます。

    "%identifier%"の種類
        %src%                               ソースファイル(パスあり)
        %src.<extension>%                   ソースファイルの拡張子を<extension>に変更した場合のファイル名(パスあり)
        %src.name.<extension>%              ソースファイルの拡張子を<extension>に変更した場合のファイル名(パスなし)
        %arg[n]%                            nは0~引数の個数+1, n=0はアプリ名, n=1は%src%に等しい
        %arg[n].<extension>%                %arg[n]%の拡張子を<extension>に変更した場合のファイル名(パスあり)
        %arg[n].name.<extension>%           %arg[n]%の拡張子を<extension>に変更した場合のファイル名(パスなし)
        %<identifier>%                      let命令で宣言された<identifier>

    Example:
    ----
    //##$ dmc %src%
    //##$ %src.exe%
    //##& set waitTime 10000
    //##    0
    //##    1
    //##    2
    //##    3
    ----
 */

import std.algorithm,
       std.array,
       std.ascii,
       std.conv,
       std.exception,
       std.format,
       std.process,
       std.range,
       std.regex,
       std.stdio,
       std.string,
       std.utf;

import core.thread;


void main(string[] args)
{
    enforce(args.length > 1);
    auto srcfile = args[1];
    long waitTimemsecs = 1000;
    string[] cmdlines;
    string delegate(string)[] argRegexReplaceDlg;
    string[string] idents = ["src" : srcfile];

    {
        auto srcFile = File(srcfile);
        foreach(line; srcFile.byLine)
            if(line.startsWith("//##")){
                immutable idup = line.dup;
                cmdlines ~= idup;
            }else
                break;
    }


    if(cmdlines.length == 0)
        throw new Exception(`Error: Input file doesn't have some command scripts. File name is "%s"`.toFormattedString(srcfile));

    Pipe cmdPipe;
    Pid cmdProcess;
    string lastProcessCode;


    static string toNameExtensionName(string filename)
    {
        return filename.match(regex(`(\w+)\.\w+`)).captures[1] ~ ".$1";
    }


    static string toExtensionName(string filename)
    {
        return filename.match(regex(`\.\w+`)).captures.pre ~ ".$1";
    }


    {
        //argRegexReplaceDlg ~= (string str) => str.replace(regex(`%src%`), srcfile);
        argRegexReplaceDlg ~= (string str) => str.replace(regex(`%src\.name\.(\w+)%`), toNameExtensionName(srcfile));
        argRegexReplaceDlg ~= (string str) => str.replace(regex(`%src\.(\w+)%`), toExtensionName(srcfile));
        
        argRegexReplaceDlg ~= (string str){
            while(1){
                if(auto m = str.match(regex(`%arg\[(\d+)\]%`))){
                    auto c = m.captures;
                    debug writeln(c);

                    immutable n = c[1].to!sizediff_t();
                    enforce(n < args.length);

                    str = str.replace(regex(`%arg\[(\d+)\]%`, "g"), args[n]);
                }else
                    return str;
            }
        };


        argRegexReplaceDlg ~= (string str){
            while(1){
                if(auto m = str.match(regex(`%arg\[(\d+)\]\.name\.(\w+)%`))){
                    auto c = m.captures;
                    debug writeln(c);

                    immutable n = c[1].to!sizediff_t();
                    enforce(n < args.length && n > 0);

                    if(auto marg = args[n].match(regex(`(\w+)\.\w+`))){
                        auto carg = marg.captures;
                        debug writeln(carg);

                        immutable toName = carg[1] ~ "." ~ c[2];
                        debug writeln(toName);
                        str = str.replace(regex(`%arg\[\d+\]\.name\.\w+%`), toName);
                    }else
                        throw new Exception(`"` ~ args[n] ~ `" does not have a extension, eval of ` ~ lastProcessCode);
                }else
                    return str;
            }
        };


        auto argnextReplaceDlg = (string str){
            while(1){
                if(auto m = str.match(regex(`%arg\[(\d+)\]\.(\w+)%`))){
                    auto c = m.captures;
                    debug writeln(c);

                    immutable n = c[1].to!sizediff_t();
                    enforce(n < args.length && n > 0);

                    if(auto marg = args[n].match(regex(`\.\w+`))){
                        immutable toName = args[n].replace(regex(`.\w+`), `.` ~ c[2]);
                        str = str.replace(regex(`%arg\[\d+\]\.\w+%`), toName);
                    }else
                        throw new Exception(`"` ~ args[n] ~ `" does not have a extension, eval of ` ~ lastProcessCode);
                }else
                    return str;
            }
        };


        argRegexReplaceDlg ~= (string str){
            while(1){
                if(auto m = str.match(regex(`%(\w+)%`))){
                    auto c = m.captures;
                    debug writeln(c);

                    if(auto p = c[1] in idents)
                        return str.replace(regex(`%\w+%`, "g"), *p);
                    else
                        throw new Exception("Invalid identifier " ~ c[0] ~ ", eval of " ~ lastProcessCode);
                }else
                    return str;
            }
        };
    }


    //現在のプロセスの終了を待つ
    void finishProcess()
    {
        if(cmdProcess !is null){
            cmdPipe.writeEnd.flush();
            auto status = cmdProcess.tryWait();

            if(!status.terminated){
                long cnt = waitTimemsecs / 100;
                do{
                    core.thread.Thread.sleep(dur!"msecs"(100));
                    status = cmdProcess.tryWait();
                    --cnt;

                    if(cnt <= 0)
                        throw new Exception("Error: Process is not ended about %s".toFormattedString(lastProcessCode));

                }while(!status.terminated);
            }

            //auto code = status.status;
            if(immutable returnCode = status.status)
                throw new Exception(`Error: %s, eval of "%s"`.toFormattedString(returnCode, lastProcessCode));
        }
    }


    //コマンドの"%<identifier>%"を置換する
    string commandReplace(string command)
    {
        foreach(immutable dlg; argRegexReplaceDlg)
            command = dlg(command);
        return command;
    }


    foreach(immutable line; cmdlines){
        if(line.startsWith("//##$")){
            finishProcess();

            lastProcessCode = line.chomp();
            cmdPipe = pipe();
            debug writeln(line);
            auto command = line.split()[1 .. $].map!commandReplace().array();

            debug writeln(command);
            try
                cmdProcess = command.spawnProcess(cmdPipe.readEnd);
            catch(Exception ex)
            {
                throw new Exception("Failed to spawn new process, eval of " ~ lastProcessCode, ex);
            }

        }else if(line.startsWith("//##&")){
            auto splits = line.split();
            enforce(splits.length >= 2);

            switch(splits[1]){
                case "set":
                    switch(splits[2]){
                        case "waitTime":
                            enforce(splits.length > 3);
                            waitTimemsecs = splits[3].to!long;
                            break;

                        default:
                            throw new Exception(`Invalid script, eval of "%s"`.toFormattedString(lastProcessCode));
                    }
                    break;

                case "let":
                    enforce(splits.length > 4);
                    enforce(splits[3] == "=", `Invalid script, eval of %s`.toFormattedString(lastProcessCode));
                    
                    immutable str = commandReplace(splits[4]);
                    idents[splits[2]] = str;
                    break;

                default:
                    throw new Exception("Invalid script, eval of %s".toFormattedString(lastProcessCode));
            }
        }else if(line.startsWith("//##")){
            if(cmdProcess !is  null){
                immutable output = line[4 .. $].find!(a => !a.isWhite)();
                cmdPipe.writeEnd.writeln(output);
                debug writeln(output);
            }else
                throw new Exception(`No process is running, eval of "%s"`.toFormattedString(lastProcessCode));
        }
    }


    finishProcess();
}


string toFormattedString(T...)(string format, T args)
{
    auto writer = appender!string();
    writer.formattedWrite(format, args);
    return writer.data;
}