MATLAB Live ScriptからMarkdownへのデータの書き出し


Live Script でデータ解析をすれば、コードとその計算結果が全部一箇所に保存できるので、これだけで十分、と思っていましたが、実際にその計算結果を利用して論文に記載しようと思うと、長大な Live Script そのままでは、コードに埋もれてしまって、肝心の数字を見つけるのがかなり鬱陶しいということを自覚しました。

数字を見つけて、Wordにコピーするだけで数日かかってしまった上、後から問題が見つかって計算をやり直したので、もうこの作業を繰り返すわけにはいかんと結論したのでした。

そこで、結局、Live Script の中で、重要な箇所について、Live Script の出力に数字を吐き出しながら、同時に、テキストファイルに markdown を書き出して、要点だけが見られるようにまとめることにしました。

私の解

正直あまりエレガントとは言えませんが、落ち着いたのはこのような形です。

もともと Live Scriptで、このような形で、セミコロンを省くことで計算結果を吐き出せていたとします。

length(A)

table(B,C,D,...
  'VariableNames',{'John','Ben','Tom'})

それをこのように書き換えます。

fid = fopen('foo.md','w','n','UTF-8');
fp = @(x,y) fprintfcodemd(fid,x,y);

fprintf(fid,'# Length of A');
s = 'length(A)';
fp(s,evalc(s));

fprintf(fid,'# Data of 3 people');
s = [...
  "table(B,C,D,...";...
  "'VariableNames',{'John','Ben','Tom'})";...
  ];
fp(s,evalc(regexprep([s{:}],'\.{3}','')));

fclose(fid);

解説

fid = fopen('foo.md','w','n','UTF-8');

'w' オプションで、'foo.md'という名前の markdownファイルを新たに作ります。既に同じ名前のファイルが存在する場合は上書きします。

'n','UTF-8'によって、Unicodeに文字コード指定します。evalc出力には頻繁に×(掛け算記号)が用いており、Unicodeに指定しないとWindowsでは文字化けが起こります。

fprintf(fid,'# Length of A\n');

これは上で作ったファイルに、markdownで見出し(#H1)を書き込んでいます。

fp = @(x,y) fprintfcodemd(fid,x,y);

これは、鬱陶しいので表記を短くするための行です。

@で無名関数(anonymous function)を作り、fpという短い名前で呼べるようにします。

fp(s,evalc(s));

関数fprintfcodemdは、markdownのコードブロック(```で挟まれた部分)に、プロンプトの記号 >>と、文字列sそのものを書き込み、その下にevalc(s)の出力、つまりsに書かれたコードに対するコマンドウィンドウの出力を書き込みます。このとき、実はevalcの出力は太字指定などの HTML タグを含んでしまっているので、関数fprintfcodemdの内部でこれを除去しています。

evalcの実行時に、sに登場する変数全てにアクセスする必要があるため、このevalcは関数fprintfcodemdに内蔵させることができません。

markdownの出力はこのようなイメージです。

# Length of A
​```
>> length(A)

ans = 

    19

​```
s = {'table(B,C,D,...';...
  '''VariableNames'',{''John'',''Ben'',''Tom''})'};

二つ目の例は、sが複数行にまたがっているのが厄介です。一行に無理やり突っ込んでもいいですが、非常に長くなってしまう場合もあるのでいつも良いとは限りません。

ここでは、仕方なく、コードを文字列のセル配列 cell array of strings としてsに格納しています。その時に、'を繰り返してエスケープせざるを得ないので可読性がとても悪化しています。

s = ["table(B,C,D,...";...
  "'VariableNames',{'John','Ben','Tom'})"];

このように、代案として、最近新しく加わった string型を利用して単引用符'の代わりに、複引用符"を使うと、あまり元のコードを乱さずに済みます。遥かに可読性が高いですし、書くのも楽ですね。

fp(s,evalc(regexprep([s{:}],'\.{3}','')))

sをそのままevalcに渡すとエラーが出るので、char型に変換する必要があります。ここでは、regexprep([s{:}],'\.{3}','')によって、一行のコマンドに直しています。

使用した関数 fprintfcodemd

function fprintfcodemd(fid,expr,evalcout)

p = inputParser;
p.addRequired('fid',@(x) isscalar(x));
p.addRequired('expr',@(x) ischar(x) || iscellstr(x) || isstring(x) );
p.addRequired('evalcout',@(x) ischar(x));
p.parse(fid,expr,evalcout);

if ischar(expr)
    expr = {regexprep(expr,';$','')};
elseif isstring(expr)
    expr = cellstr(expr);
end

% print to file fid

fprintf(fid,'\n```\n>> ');
fprintf(fid,'%s\n',expr{:});
evalcout_ = regexprep(evalcout,'<[/\w][^>]*[\w"]>',''); % get rid of HTML tags
fprintf(fid,evalcout_); %
fprintf(fid,'```\n\n');

% print to Command Window
fprintf('\n>> ');
fprintf('%s\n',expr{:});

%fprintf(['>> ',expr,'\n']);
fprintf(evalcout);
fprintf('\n');

end