ぺーぱーふぇいす

雑記と備忘録。私はプログラマ。

Meryを即席文字列加工マッスィーンにするためにしていること

SEO対策もへったくれもなさそうなタイトル。
まあ、個人用のカンペみたいなもんだしいいかな。

まず、「どういうこと?」って言われれば、Meryで文字列加工用のスクリプトPythonで書いてそのまま直接実行して結果を出力するよってこと。

例えば、リスト羅列された文字の行頭に3桁0埋めのIDを振るよって場合(GIFアニメ)。

f:id:PaperFace:20180303020902g:plain
裁判処理

Meryで編集中の文書をコピーしてそのままRAW文字列として貼り付け。加工用のスクリプトを直接書く。
書いたスクリプトをそのまま実行して、Meryのアウトプットバーへ出力された結果を元となった文書に貼り付けている。

じゃあ、以下だらだらと前置き説明とやることとか。

前置き

例えば、手元に程々のレコード量のテキストファイルがある。
これを加工して利用する必要がある際、普段あなたはどういったツールを使って、どのように加工することをベターとしているか。

といった問題。
UNIX系の使い手だったら、AWKを使うだろう。
ここでの対象のファイルがCSVだったらqを使うというのもアリかもしれない。
一旦、SQLite、あるいはローカルに構築している実験用のDBMSCSVをぶちこんでクエリを投げるとか。
もっとお手軽にExcelとかLibre OfficeのCalcを使うとか。
正規表現が使えるなら各種テキストエディタで、置き換えとかを繰り返していけばお目当てのデータに加工することができるかもね。

どの方法が一番いいかというのは個人が持つそれぞれのツールに対するスキルによると思う。AWK教の人間ならAWKを使うだろうし、そうでない人はそうでないツールを単一あるいは複数組み合わせて目的のフォーマットに加工することだろう。
ちなみに私はAWKのことがわかってない。

私が一番良いなと思うのは、C#で自分が思う加工用のアプリケーションを作ってしまうことだ。.NET Frameworkを使えば、ファイルとしての読み込みや文字列としての扱い、正規表現などが全部入りで、文字列を加工するという手段としてはかなり楽だ。
私の思考が偏っているのだと思うけど、こうした作業をPCに行わせる際は、コマンドライクな命令を組み立てるような手順よりも、いっそのことオブジェクト思考な言語でコーディングしてしまった方が個人的にはしっくりくる

だが、Visual Studioを用意しないといけないし(VS無しのC#コーディングとかは無しでお願いします(^q^))、コードを書き、コンパイルするという手順が発生するので「こうしたアプリケーションを作るほどでもない」というケースのほうがほとんどだと思う。
じゃあ、何かと言えば「お手軽で使い勝手の良いスクリプトを書いて、楽に実行できるのが一番エコだ」と思った。
んで、一番使っているエディタはMeryなので、Meryカリカリ書いたロジックがそのまま動いて、出力結果が\コンニチハ/すればきっとそれは素敵なことだろう」と思った。

環境の準備

構想

スクリプトPythonとした。
Pythonをセットアップし、Meryで「今書いているテキストをスクリプトとして実行するマクロ」を書く。
このマクロをMeryのキーボードショートカットに登録して、簡単に呼び出せるようにしておく。

Pythonを選定したいろいろな理由はあるけれど、それはこのエントリの最後らへんにうだうだ書くよ。

Python(Anaconda)のセットアップ

個人的に手軽なAnacondaをセットアップする。
インストーラも準備されているので、セットアップは簡単だと思う。
Anacondaはいろいろなパッケージが入っているので導入しておくと何かと便利。
ただ、Anacondaは容量がデカイし、最小限のコンポーネントしか入れない主義の人はオリジナルのPythonもしくはAnacondaの軽量版であるMinicondaを入れるといいかもしれない。

Python Release Python 3.6.4 | Python.org

Miniconda — Conda

マクロの作成

んで、マクロ。これ。
スクリプトの最初の方で、PYTHON_PATHPYTHONW_PATHとして、Python.exePythonw.exeまでのパスを指定している点に注意(理由は後述)。

#title="スクリプト実行"
// -----------------------------------------------------------------------------
// 今編集中のドキュメントをスクリプトとして実行
// about:  今編集しているテキストをスクリプトとして実行します。
//         拡張子によってどの言語か判定します。
// create: 2018/02/20 PaperFace1000
// update: 
// ---------------------------------------------------------------------------

// スクリプトエンジンパス
var PYTHON_PATH = "\"C:\\ProgramData\\Anaconda3\\python.exe\"";
var PYTHONW_PATH = "\"C:\\ProgramData\\Anaconda3\\pythonw.exe\"";

// デフォルト拡張子
var DEFAULT_EX = ".py";

main();

// 日付フォーマット
function formatDate (date, format) 
{
  format = format.replace(/yyyy/g, date.getFullYear());
  format = format.replace(/MM/g, ('0' + (date.getMonth() + 1)).slice(-2));
  format = format.replace(/dd/g, ('0' + date.getDate()).slice(-2));
  format = format.replace(/HH/g, ('0' + date.getHours()).slice(-2));
  format = format.replace(/mm/g, ('0' + date.getMinutes()).slice(-2));
  format = format.replace(/ss/g, ('0' + date.getSeconds()).slice(-2));
  format = format.replace(/SSS/g, ('00' + date.getMilliseconds()).slice(-3));
  return format;
}

function main()
{
    var fso = new ActiveXObject('Scripting.FileSystemObject');
    var sh = new ActiveXObject( "WScript.Shell" );
    
    if(Document.Name == ""){
        var scriptDir = fso.BuildPath(fso.GetParentFolderName(ScriptFullName), "exec_scripts");
        var outputFileName = Prompt("このスクリプトは保存されていません。ファイル名を決めてください。", 
                                    fso.BuildPath(scriptDir, formatDate(new Date(), "yyyyMMdd_HHmmss_SSS" + DEFAULT_EX)));
        if(!fso.FolderExists(scriptDir)){
            fso.CreateFolder(scriptDir);
        }
        Document.Save(outputFileName);
    }
    if(!Document.Saved){
        Document.Save();
    }
    
    var workDir = fso.GetParentFolderName(Document.FullName);
    var fileName = Document.Name;
    
    sh.CurrentDirectory = workDir;
    var cmd;
    
    switch(fso.GetExtensionName(fileName).toLowerCase()){
        case "py":
            cmd = PYTHON_PATH + " \"" + fileName + "\"";
            Document.Mode = "Python";
            break;
        case "pyw":
            cmd = PYTHONW_PATH + " \"" + fileName + "\"";
            Document.Mode = "Python";
            break;
        default:
            Quit();
    }
    
    //Alert(cmd);
    
    var output = sh.Exec(cmd);
    var stdout = output.StdOut.ReadAll();
    var stderr = output.StdErr.ReadAll();
    
    OutputBar.Visible = true;
    OutputBar.Writeln(formatDate(new Date(), "yyyy/MM/dd HH:mm:ss.SSS") + ">" + cmd);
    if(stdout != ""){
        OutputBar.Writeln(stdout);
    }
    if(stderr != ""){
        OutputBar.Writeln(stderr);
    }
}

基本的な処理の流れはこんな感じ。

  1. 現在Mery上でアクティブな文書がファイルとして保存されているかチェック。
  2. ファイルとして保存されていない場合はダイアログを表示してファイル名の入力、保存を促す。デフォルトパスは<このマクロが設置してあるディレクトリ>\\yyyyMMdd_HHmmss_SSS.pyが提示される。ファイルが既に存在する場合は上書き保存する。
  3. アクティブな文書のパスを取得し、拡張子がpyであればPython.exeに。拡張子がpywであればPythonw.exeスクリプトとして実行するよう、コマンドを実行する。
  4. コマンドの結果として標準出力、あるいはエラー出力を受け取ってMeryのアウトプットバーに出力する。
  5. 以上

この手のマクロはMeryの公式Wikiに既に掲載されている。

拡張子からコンソール実行 - MeryWiki

ただ、人によってはPythonの2.x系と3.x系を共存させている人も居たりとかも居ると思うので、あえてWindows OSのパス頼みじゃなくて直接指定することにしている。
また、あとで整理すればいいやの概念で、書いたスクリプトはデフォルトとして指示されるフォルダにガンガン保存してとにかく実行する作りにした。

使う

通常のMeryマクロと同じ。
マクロを登録して、ショートカットキーにでも登録しておけばガンガン実行できるかな。

私個人はF5キーをショートカットキーとして割り当ててる。
いじょー。

余談

なんかDOS窓がチラチラする

デフォルトの動作だといったんDOS窓が立ち上がって、その後にMeryのアウトプットバーにいろいろと出力されるというイメージ。
神経質な人は、DOS窓がちらちら見えてるのが嫌かもしれない。回避策としては、シェルに渡すコマンドをもっと細工したりすればなんとかできるかも(下記参考)。
私は気にならないので特に回避策は設けてない。気が向いたらやるかも。たぶん向かない。

WScript.ShellのExec()でコンソールアプリを非表示で実行する。: Windows Script Programming

ちなみに、Pythonw.exeに渡せばDOS窓は開かない。
このマクロでも、対象となるスクリプトの拡張子がpywだった場合は、Python.exeではなくPythonw.exeに渡すようにしている。
しかし、Pythonw.exeスクリプトを実行した場合、標準出力とエラー出力は4,096byte以上出力できない(フリーズする?)らしい(下記参照)。

pythonメモ  pythonw.exeの注意点 - LinuxとかBlenderとかVol2

なので、pywを使うのは出力結果がファイルベースだったり、Tkinterを使って作ったGUIで入力出力しちゃうスクリプトを作った時くらいかな。

なんでPythonにしたのか

いろいろ試してこんな感じ。
偏見丸出しなのはごめんね。

言語 試してみた所感と偏見
VBScript Windows標準でセットアップの手間が無いけど、VBライクの文法嫌い。没。
JScript JavaScriptっぽいけど、現代のJavaScriptと比べるとだいぶ違う仕様があってそれにぶつかる度にイライラする。没。
Kotlin 言語としては結構好き。Javaのパワーを使える(POIとか)。スクリプトとしても実行できるけど、結果出力までが遅かった(コンパイルが走ってる?)ので没。
Go 言語としては結構好き。ただ、MeryのClassViewプラグインにて対応していなかったのと、個人的な知見の浅さ(学習コスト)から没。
Node.js JavaScriptで書けるという強みあり。ただし、アウトプットバーへの出力が文字化けするので、その回避のめんどくささと、Anacondaとくらべて最初から入れたいモジュールの数的に没。

他、いろいろと使えそうなスクリプト言語はあるよね。Rubyとかさ。
ここらへんは個人が好きな言語で良いと思う。ぶっちゃけ。
でも、まあMeryのキーワード強調が標準で入ってるとか、MeryのClassViewプラグインに対応しているとか、アウトプットバーへ出力した時に文字化けしないかとか導入する障壁が一番少なかったのがPythonだったかな。
あとは、Pythonは結構レスポンス良かった。速い。
「えぇ~? Python速いかぁ?」って思う人も居るかも。
まあ、少なくとも「私がMeryから実行したいスクリプト」はゴリゴリの処理ではなく、文字列を加工したりテストデータを作成させたりするくらいなので、それよりも複雑なロジックを書いたら結果は違うかもしれない。ただ、私がMery上で書くのはその程度のスクリプトで、もっとゴリゴリした処理を書くにはVisual Studio使うと思う。
先程言ったように、私はこのマクロをF5キーに登録している。私がカリカリとPythonスクリプトを書いて、「えいっ(。・ω・)σ ★」とF5キーを押してからその結果が標準出力として返ってくるレスポンスは良かった。
以上から、一番私のニーズとMeryの相性の良さ的にはPythonかなぁと。

あと、GUIライブラリとしてtkinterがついてくるのが個人的にデカイ。
まあ、Windowsインストーラからセットアップした時のオプションによるのだろうけど、導入も簡単だし、それほど難しくないし、好き。

csi.exeが欲しい

前置きでうだうだ書いたけど、個人的にはC#でexeを作っちゃいたいってのが本心。
というか、C#スクリプトにしたい。
そういう手段自体は既に存在していて、Visual Studio 2015あたりをインストールするとcsi.exeというC#のREPLがついてくるんだよね。

csi.exeコマンド登場! C#スクリプト(.csx)やREPLを動かそう - Build Insider

これでC#スクリプト(拡張子はcsx)を書けるから、これで.NET Frameworkの力を使えるんだけど、いかんせんMicrosoftがこのcsi.exe単体で配布してくれてないのよね(´ . .̫ . `)
Visual Studioが入ってるPCからcsi.exeだけ抜き出して、他のPCに設置してスクリプトエンジンとして利用……なんてことはライセンス的にアウトかもしれんからやりたくないし。
Windowsにおけるスクリプト環境は、とにかくこれが来てくれたら個人的に最強だと思うんだけどねー……ヴァー('A`)

いや、AWKやれよ

(╭☞•́⍛•̀)╭☞それな。
けど、学習コストへの兼ね合いと使ってるのがWindowsメインなだけにAWKは敷居が高いんだよね。
私、AWKとかPowerShellとかみたいなコマンドにオプションいっぱいくっつけて処理させるような感じの体系が苦手なんだよね。
まあ、いつかやるよ、いつか。