WSH WSF JScript・VBScript で、YAML をパースする方法(js-yaml)
JScript や VBScript で yaml ファイルをパースしたいとき、
wsf形式と外部パッケージのjs-yaml
で、簡単に実現できます。
github.com
実装
yamlファイル。(sample.yaml)
文字コードは、SJISを使用。
# 設定ファイル config: title: "AppName" width: 1280 height: 720
wsf形式が関連付けされていない環境があるので、
サンプルは適当なバッチに埋め込んで使います。
<!-- : @setlocal&pushd %~dp0 @cscript //nologo "%~f0?.wsf" %* || pause @popd&endlocal&exit /b --> <job> <script src="https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.5.14/es5-shim.min.js" /> <script src="https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.5.14/es5-sham.min.js" /> <script src="https://cdnjs.cloudflare.com/ajax/libs/js-yaml/3.13.1/js-yaml.js" /> <object id="shell" progid="WScript.Shell" /> <object id="fso" progid="Scripting.FileSystemObject" /> <script language="JScript"> WSH.Quit(function() { try{ // 読み込み var yamlFile = fso.OpenTextFile("sample.yaml", 1, true); var yamlText = yamlFile.ReadAll(); yamlFile.Close(); var doc = jsyaml.load(yamlText); // 表示 WSH.Echo(doc.config.title); WSH.Echo(doc.config.width); WSH.Echo(doc.config.height); // Enter入力待ち WSH.StdIn.ReadLine(); } catch(e){ WSH.Echo(e.name + ":" + e.message); return -1; } return 0; }()); </script> </job>
VBScript 版
<script language="VBScript"> ' 読み込み Set yamlFile = fso.OpenTextFile("sample.yaml", 1, True) yamlText = yamlFile.ReadAll yamlFile.Close Set doc = jsyaml.load(yamlText) ' 表示 WSH.Echo(doc.config.title) WSH.Echo(doc.config.width) WSH.Echo(doc.config.height) ' Enter入力待ち WSH.StdIn.ReadLine ' 解放 Set yamlFile = Nothing Set doc = Nothing </script>
仕組み
js-yaml
では、ES5に対応した環境が求められますが、
wsf 形式は、ES3程度の機能しか使えないのでes5-shim
ライブラリを使って
擬似的にES5環境を扱えるようにします。
読み込んだ後は、jsyaml
オブジェクトを扱うだけです。
その他
オフライン環境で使いたい場合など、
ライブラリの参照にCDNを使いたくなければ、ローカルに保存して使いましょう。
<script src="./es5-shim.min.js" /> <script src="./es5-sham.min.js" /> <script src="./js-yaml.js" />
以上、おわり。
WSH JScript で、高精度に処理時間を計測する(Performance API)
はじめに
JScriptで、スクリプトの処理時間を計測するとき、
Date オブジェクトを使って計測することがありますが、これはミリ秒単位の精度です。
厳密な速度計測が不要であれば、ミリ秒もあれば事足りますが、
より精度の高いマイクロ秒単位が必要であれば、
Web API (Performance API) の Performance インターフェースを使います。
実装
計測対象の関数は、ここから拝借。
フィボナッチ数を出力する関数を作る - Qiita
function do_something() { var fib = function (n) { return n > 2 ? fib(n - 1) + fib(n - 2) : 1; }; fib(33); }
Performance インターフェースによる計測
JScript環境の、Performance インターフェースは、
JSON オブジェクト等と同様で、htmlfile オブジェクトから取得できます。
WSH.Quit(function() { try{ // Performance を取得 var htmlfile = WSH.CreateObject("htmlfile"); htmlfile.write('<meta http-equiv="x-ua-compatible" content="IE=Edge"/>'); var performance = htmlfile.parentWindow.performance; htmlfile.close(); // 計測 var startTime = performance.now(); do_something(); var endTime = performance.now(); // 結果 // 浮動小数が得られる。小数点以下が、マイクロ秒。 // (小数第1位までしか得られない?) WSH.Echo((endTime - startTime) + " ms"); } catch(e){ WSH.Echo(e.name + ":" + e.message); return -1; } return 0; }());
過去の手法
Date オブジェクトによる計測
WSH.Quit(function()
{
try{
// 計測
var startTime = new Date();
do_something();
var endTime = new Date();
// 結果(※ これでは、ミリ秒に丸められた結果しか得られない)
WSH.Echo((endTime.getTime() - startTime.getTime()) + " ms");
}
catch(e){
WSH.Echo(e.name + ":" + e.message);
return -1;
}
return 0;
}());
その他メモ
バッチコマンドの計測であれば、powershell の Measure-Command が手軽です。
powershell -c (Measure-Command { (ここに計測するコマンド) })
WSH JScript で、JSONファイルをパースする(Chakra を利用する場合)
メモ
既定のJScript でJSONオブジェクトを利用する方法の別解。
Chakra エンジンを使うとJSONオブジェクトが定義済み
で、そのまま使える模様。
実装
エンジンを指定するため、適当な .bat
に埋め込んで使います。
0</*! :: @%windir%\System32\cscript.exe //nologo //E:{1B7CD997-E5FF-4932-A7A6-2A9E636DA385} "%~f0" %*||pause @pause rem 入力待機 @exit /b */0; (()=>{ try { // JSONをパース const jsonTxt = `{"Application": {"title": "SampleApp", "width": 1280, "height": 720}}`; let rootObj = JSON.parse(jsonTxt); // 結果を表示 let app = rootObj.Application; WSH.Echo(`${app.title}\n${app.width}\n${app.height}`); } catch(e){ WSH.Echo(e.name + " : " + e.message); } finally{} })();
結果
SampleApp 1280 720
以上、おわり。
関連
WSH JScript CScript プログレスバー
はじめに
軽く検索しても手軽なサンプルが見つからなかったので、
CScript と JScript を使ったコマンドライン環境向けに、プログレスバーを用意します。
↓ のように動きます。
実装
最低限の機能があれば良いので、設定できるオプションは下記だけ。
画面更新の仕組みとしては、C++等と同様で、
制御文字CR(\r)を使っています。(行頭に戻して書き直しするだけ。)
var ProgressBar = (function(){ function ProgressBar() { this.MaxValue = 100; this.BarWidth = 30; this.Label = "progress"; }; ProgressBar.prototype.Update = function(value){ var rate = value/this.MaxValue; var val = Math.ceil(Math.min(Math.max(1,rate*this.BarWidth),this.BarWidth)); var barComplete = Array(val+1).join("="); var barIncomplete = Array(this.BarWidth-val+1).join("-"); var isProcessing = 1>rate; WSH.StdOut.Write("\r"+this.Label+" ["+barComplete+barIncomplete+"] " +(isProcessing?(rate*100).toFixed(2)+"%":"complete\n")); return isProcessing; }; ProgressBar.prototype.Complete = function(){this.Update(this.MaxValue)}; return ProgressBar; }());
使い方
進捗状態を更新するときは、Update
。
強制的に完了にしたければ、Complete
メソッドを呼びます。
WSH.Quit(function() { try{ var prog = new ProgressBar(); { prog.Label = "サンプルA:整数\t\t"; for (i=0; prog.Update(i); i+=2) { WSH.Sleep(100); } } { prog.Label = "サンプルB:浮動小数\t"; prog.MaxValue = Math.PI; for (i=0; prog.Update(i); i+=0.1) { WSH.Sleep(100); } } { prog.Label = "サンプルC:バーの幅変更\t"; prog.BarWidth = 50; for (i=0; i < prog.MaxValue; i+=0.1) { prog.Update(i); WSH.Sleep(100); } // 強制する場合 prog.Complete(); } } catch(e){ WSH.Echo(e.name + ":" + e.message); return -1; } finally{ // 3秒待機 WSH.Sleep(3000); } return 0; }());
おわりに
時間のかかる処理ではプログレスバーを出す方が親切ですが、
コンソール画面(CUI, CLI)では、ログを出していればとりあえず処理が進んでいるのが目視できるのでプログレスバーは必須では無いと思います。
WSH wsf の resource を外部ファイルに切り出す(WSC を使った実装の分割)
はじめに
WSH(.wsf) では、<resource>
タグを使って任意のテキストデータをスクリプトに埋め込みできます。
しかし、設定ファイル用途で使用したり、ある程度容量が増えてきたりすると、外部ファイルに分割したくなります。
こういった場合ファイルの分割は、Windows Script Component (WSC) の仕組みを使って実現できます。
WSC は、WSF形式のスクリプトをCOMコンポーネントとして扱えるようにするテクノロジーですが、
モニカという機能を使って、レジストリに登録すること無くコンポーネントを利用できます。
つまり、<resource>
タグごとコンポーネント化することで、外部ファイルに切り出せるということです。
やり方
モニカを使ったコンポーネントの取得には、
WSH(JScriptやVBScript)が公開している GetObject
関数の引数に script:<wscの絶対パス>
を指定します。
wscのパスは、相対パスだとエラーになります。
その他、拡張子は wsc である必要は無く xml 形式として誤りが無ければ任意の拡張子が指定可能です。(サンプルでは、拡張子を xml とします。)
sample.bat
(スクリプト側) resource.xml
(リソース側) は、同じディレクトリに配置して動かしています。
Workspace ├─sample.bat └─resource.xml
スクリプト側
簡素化のために、bat ファイルに埋め込んでいますが、処理内容はシンプルです。
バッチでプロセス環境変数に、wsc(resource.xml) のパスをsetしておいて、
JScriptで、setした環境変数を読み取っています。
<!-- : @echo off&title %~n0&setlocal&pushd %~dp0 rem リソース(wsc)のパス set resource_name=resource.xml set resource_path=%~dp0%resource_name% set resource_moniker=script:%resource_path% rem job実行 cscript //nologo "%~f0?.wsf" %* || pause popd&endlocal&exit /b --> <job> <object id="shell" progid="WScript.Shell"/> <script language="JScript"> WSH.Quit(function() { try{ // リソース読み込み var procEnv = shell.Environment("Process"); var rc = GetObject(procEnv("resource_moniker")); // とりあえず、表示 var result = rc.Get("sln"); WSH.Echo(result); // (ここで、リソースを使ってやりたい処理など) } catch(e){ WSH.Echo(e.name + ":" + e.message); return -1; } finally{ // 表示確認のため、3秒待機 WSH.Sleep(3000); } return 0; }()); </script> </job>
リソース側
<resource id="識別子"><![CDATA[
~ ]]></resource>
の間が、埋め込むテキストです。
拡張子を .wscでなく、.xmlにしているのは、ダブルクリックでレジストリ登録が走らないようにするためのフェイルセーフ。
<?xml version="1.0" encoding="utf-16" ?> <component> <!-- 下記2行で、リソース取得関数を公開 --> <public><method name="Get"><PARAMETER name="id"/></method></public> <script language="JScript">function Get(id){return getResource(id)}</script> <!-- 適当なリソース --> <resource id="sln"><![CDATA[ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.28010.2016 MinimumVisualStudioVersion = 10.0.40219.1 Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {753BEE69-EAF9-44D4-86E7-78DDC490BA05} EndGlobalSection EndGlobal ]]></resource> </component>
あとがき
今回は、任意のディレクトリ構成でVisualStudio用のプロジェクトを作成するスクリプトで使用した方法の備忘録です。
設定ファイル程度であれば htmlfile オブジェクトから、JSONオブジェクトが取れるので.jsonで取り扱うことも出来ますが、
複数の既成フォーマットをひとまとめにしておくケースでは、テキストリソースの方が都合が良い訳です。
一応別解として、ES3程度の機能しかないJScriptでも正規表現を駆使したテクニックで、
ヒアドキュメントのような事ができるので、
<script>
タグのsrc属性でリソース分割は可能ではありますが、これはランタイムで本来不要なテキストの加工が走るので好みません。
そういうわけで、WSCを使った方がパフォーマンス的にもコードの読みやすさとしても、スマートになるかと思います。
終わり。
C++ CLI Processクラスから HINSTANCE を取得する
メモ
C++/CLI 環境で、Process クラスで起動したプログラムのインスタンスハンドルを取得する方法。
Handleプロパティが、そのまま HINSTANCE として扱える。
実装
// 「ペイント」を起動 auto proc = gcnew System::Diagnostics::Process(); proc->StartInfo->FileName = "mspaint.exe"; auto result = proc->Start() && proc->WaitForInputIdle(); if (!result) { // 起動に失敗 return; } // インスタンスハンドルを取得 auto hInst = static_cast<HINSTANCE>(proc->Handle.ToPointer());
JScript.NET をバッチに埋め込む方法
はじめに
Microsoft製のドマイナー言語、JScript .NET をバッチファイルに埋め込んで実行する方法です。
JScript .NET は、スクリプト言語である WSH版 JScript の後継的な位置づけでありながら、コンパイル必須という扱いづらい言語なので、
コンパイル処理と実行を一度に処理できるように、バッチファイルに埋め込んでしまおうというお話。
実装
おまじない部分
バッチに埋め込んで実行するためのコード。
コンパイルに成功したら実行するだけ。
@if(0)==(0) for /f "delims=" %%i in ('where /r %windir%\Microsoft.NET jsc.exe^|sort') do @set jsc=%%i @"%jsc%" /nologo /out:"%~dp0%~n0.exe" "%~f0"&&"%~dp0%~n0.exe" %* @exit /b&@end
バッチをUTF-8で保存する場合
コンパイラに文字コードを知らせるため、/codepage
オプションを指定します。
UTF-8のコードページは、65001
です。
@"%jsc%" /nologo /codepage:65001 /out:"%~dp0%~n0.exe" "%~f0"&&"%~dp0%~n0.exe" %*
多重コンパイルしたくない場合
実行毎にコンパイルが走ると、その分起動に時間がかかってしまうので、
実行ファイル名にハッシュ値を入れて、コンパイルが必要かどうか判定します。
@if(0)==(0) echo off&for /f "delims=" %%i in ('certutil -hashfile "%~f0"^|find /v ":"') do set app_path=%~dp0%~n0[%%i].exe if not exist "%app_path%" del "%~dp0%~n0[*].exe">nul 2>&1&for /f "delims=" %%i in ('where /r %windir%\Microsoft.NET jsc.exe^|sort /r') do "%%i" /nologo /out:"%app_path%" "%~f0"&&goto:run||pause&goto:EOF :run "%app_path%" %*&exit /b &@end // 以下に実装を書く import System; /** * エントリーポイント用 */ Environment.Exit(function():int{ var exitCode:int = 123; try{ Console.WriteLine("Hello JScript.NET!!"); Console.ReadKey(); }catch(e){ exitCode = int.MaxValue; print(e), Console.ReadKey(); }finally{ } return exitCode; }());
その他
別解として、
実行ファイルを作らず powershell 経由でインメモリ実行する例。
qiita.com