//##$ 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;
}