2018年8月21日火曜日

外部スクリプト化と、それに伴うデバッグ用のサンプル

HTAからAdodb.Connectionを使用するとADO警告メッセージが表示されてしまうため、HTA内部のスクリプトから外部スクリプトファイルを作成してAdodb.Connectionを使用する部分のみ外部化して警告メッセージの表示を回避する場合など、外部化が必要になるケースはたまにあります。

外部化は警告メッセージの表示などを回避できるメリットがある一方で、エラーメッセージも表示されないという特徴もあります。
意図的に隠したい場合は、それもメリットになりますが、プログラム作成や改良の段階ではエラーメッセージが表示されないとデバッグに必要な情報が得られず、意図しないエラーの存在に気付くのが遅れて作業が捗りません。

デバッグ用に、エラーに関する情報を取得するためのメソッドを作成しました。


汎用スクリプト = function(){
// 一時的なスクリプトファイルを作成して処理を外部化する場合、
// 汎用スクリプトファイルのPathではなく関数を埋め込む方が良さそう。
外部スクリプトファイル=function(){
var returnObj = {
作成:function(メイン関数){
var path一時的 = AXO.fs.pathTMP+'/'+getTime()+'_外部スクリプト.js'
var str出力 = [
'main = '+メイン関数,
'汎用スクリプト = '+汎用スクリプト,
'汎用スクリプト()',
'!'+function(){
var arg = WScript.Arguments
var path = arg[LEN] ? arg(0) : ''
var 引数
eval('引数 = ' + AXO.fs.Read(path)+'.引数')
var returnValue = main(引数)
if(path){ AXO.fs.Write(path, toJSON({returnValue:returnValue, 正常終了:true})) }
}+'()'
].join('\r\n')
AXO.fs.Write(path一時的, str出力)
return path一時的
},
実行:function(path, 引数, intWindowStyle, func完了後){
// デフォルトでは非同期モードで実行する。
// func完了後が渡されている場合は、外部スクリプトが終了するまで呼び出し元は待機する。
var sw同期 = func完了後 ? true : false
var path引数受け渡しファイル = 引数と戻り値の受け渡しファイルを作成(引数)
var func後始末=function(sw打ち切り){
var objRV
eval('objRV = '+AXO.fs.Read(path引数受け渡しファイル))
var flagエラー
if(sw同期 && !objRV.正常終了){
// 同期モードで実行しない場合は、外部スクリプトの終了を待たずに後始末が始まるので
// 正常終了の結果を取得することはできない。だからsw同期がtrueの場合のみ、正常終了したか否かをチェックする。
// 外部スクリプトで何らかのエラーが発生し、引数受け渡しファイルが更新されていないと思われる。
flagエラー = true
}
AXO.fs().DeleteFile(path引数受け渡しファイル)
if(flagエラー){ return -1 }
if(func完了後){ return func完了後(objRV.returnValue) }
}
AXO.shell().run('cmd /C cscript "'+path+'" "'+path引数受け渡しファイル+'"', intWindowStyle, sw同期)
return func後始末()
},
削除:function(path){ AXO.fs().DeleteFile(path) },
デバッグ用:function(func, 引数){
// 外部スクリプトファイルにすると論理エラー等があっても
// エラーメッセージは基本的に表示されない。デバッグが大変なので
// 外部化する部分に論理エラー等があるかテストするのが、このメソッド。
// エラー箇所があったら、その行番号に対応する行テキストも出力する。
// 「実行」で使用している「WScript.Shell」の「Run」メソッドではエラーの詳細が取得できないが
// 「Exec」メソッドには以下の特徴があり、一長一短の関係。
//  ・呼び出し先の完了を待つことが出来ない … StdOut.ReadAll()を使えば一応待てる。
//  ・intWindowStyleが選べない。
var path外部スクリプト = this.作成(func)
var path引数受け渡しファイル = 引数と戻り値の受け渡しファイルを作成(引数)
var cmd = 'cscript //Nologo "'+path外部スクリプト+'" "'+path引数受け渡しファイル+'"'
var objExec = AXO.shell().exec(cmd)
var returnObj = {
StdOut : objExec.StdOut.ReadAll(),
StdErr : objExec.StdErr.ReadAll()
}
// エラーがある場合「C:\ … \外部スクリプト.js(3, 3) Microsoft JScript 実行時エラー …」のような文字列が出力される。
// 一つ目の数字がエラー発生行
if(returnObj.StdErr.match(/^[^\n]+\((\d+),/)){
returnObj.エラー行 = AXO.fs.RAS(path外部スクリプト)[RegExp.$1-1].replace(/^\s+/g,'')
}
this.削除(path外部スクリプト)
this.削除(path引数受け渡しファイル)
return returnObj
},
デバッグ方法のサンプル:function(){
var funcエラー=function(引数){
// Aは宣言されていない、というエラーが出る筈
WScript.Echo(A)
}
var funcOK=function(引数){
WScript.Echo(123)
}
var 引数 = 1
// 「デバッグ用」ではエラーが発生した場合、発生行やエラーの内容などが取得できる。
WScript.Echo(toJSON(外部スクリプトファイル.デバッグ用(funcエラー,引数))) // 戻り値:{StdOut:'', StdErr:'C:\\…\\外部スクリプト.js(3, 6) …', エラー行:'WScript.Echo(A)'}
WScript.Echo(toJSON(外部スクリプトファイル.デバッグ用(funcOK ,引数))) // 戻り値:{StdOut:'123\r\n', StdErr:''}
// 「実行」ではエラーが発生しても「func実行後」を渡していなければ「実行」メソッドは何もreturnしない。
// 「func実行後」を渡していても、returnの中身はエラーがあったことを示す「-1」のみ。
var path = 外部スクリプトファイル.作成(funcエラー)
WScript.Echo(外部スクリプトファイル.実行(path, 引数, 1)) // 戻り値:(無し)
WScript.Echo(外部スクリプトファイル.実行(path, 引数, 1, funcエラー)) // 戻り値:-1
外部スクリプトファイル.削除(path)
var path = 外部スクリプトファイル.作成(funcOK)
WScript.Echo(外部スクリプトファイル.実行(path, 引数, 1, funcOK)) // 戻り値:(無し)
外部スクリプトファイル.削除(path)
}
}
var 引数と戻り値の受け渡しファイルを作成=function(引数){
var path = AXO.fs.pathTMP+'/'+getTime()+'_外部スクリプト用引数受け渡しファイル.txt'
AXO.fs.Write(path, toJSON({引数:引数}))
return path
}
return returnObj
}()
var ActiveXObject関係=function(){
AXO = function(短縮名, 正式名){
var AXO = new ActiveXObject(正式名)
return arguments.callee[短縮名] = function(){ return AXO }
}
var fso = AXO('fs', 'Scripting.FileSystemObject')
var fs = fso()
fso.Read = function(p){var r=fs.OpenTextFile(p),v=r.AtEndOfStream?'':r.ReadAll();r.Close();return v}
fso.RAS = function(p){return fso.Read(p).split('\r\n')}
fso.Write = function(path,v){with(fs.CreateTextFile(path)){Write(v);Close()}}
fso.pathTMP = fs.GetSpecialFolder(2)
var obj = AXO('shell', 'WScript.Shell')
var shell = obj()
obj.getDir = function(path,swDir){
var arr=shell.exec('cmd /C dir /A'+(swDir?'':'-')+'D /B "'+path.replace(/[/]/g,'\\')+'"').StdOut.ReadAll().split('\r\n').reverse()
!arr[0] && arr.shift()
return arr.reverse()
}
}
ActiveXObject関係()
LEN='length'
for0L = function(arr,fun){for(var i=0,L=arr[LEN],res;i<L;i++){if(res=fun(i,arr[i])){return res}}}
forIn = function(obj,fun){var name,res; for(name in obj){if(res=fun(name, obj[name])){return res}}}
getTime=function(d){return (d || new Date()).getTime()}
toJSON=function(){
var main=function(o){ var t=gt(o); return o===null ? 'null' : objT[t] ? objT[t](o) : objC[gc(o)](o) }
var gt=function(o){return typeof(o)}, gc=function(o){return o.constructor}, objT={}, objC={}, fStr, nt='\n\t'
var n2nt = function(v){return (v+'').replace(/\n/g,nt)}
objT[gt('')] = fStr = function(s){return '"'+(s+'').replace(/\\/g,'\\\\').replace(/\r/g,'\\r').replace(/\n/g,'\\n').replace(/"/g,'\\"')+'"'}
objT[gt(0)] = objT[gt(true)] = objT[gt(gt.und)] = objC[gc(/1/)] = function(v){return (''+v).toLowerCase()}
objC[gc(new Date())] = function(o){return 'new Date("'+o+'")'}
objC[gc(gc)] = function(o){return インデント調整(o)}
objC[gc({})] = function(o){
if(循環.名前push(o)){return '"参照の重複"'}
var a=[], i=0
forIn(o,function(n,v){ a[i++] = fStr(循環.名前=n)+':'+n2nt(main(v)) })
循環.pop()
return '{'+nt+a.join(','+nt)+'\n}'
}
objC[gc([])] = function(o){
if(循環.名前push(o)){return '"参照の重複"'}
var a0=[], s, a1=[], i=0
for0L(o,function(i,v){a0[循環.名前=i]=n2nt(main(v))}), s='['+nt+a0.join(','+nt)+'\n]'
forIn(o,function(n,v){ !a0[n] && (a1[i++] = 'a['+fStr(循環.名前=n)+']='+n2nt(main(v))) })
循環.pop()
return a1[LEN] ? (インデント調整(function(){
var a=sss
aaa111
return a
})+'()').replace(/sss/,n2nt(s)).replace(/aaa111/,a1.join(nt)) : s
}
objC[gc(function(){})] = function(o){
if(循環.名前push(o)){return '"参照の重複"'}
var a1=[], i=0
forIn(o,function(n,v){ a1[i++] = 'f['+fStr(循環.名前=n)+']='+n2nt(main(v)) })
循環.pop()
return a1[LEN] ? (インデント調整(function(){
var f=sss
aaa111
return f
})+'()').replace(/sss/,n2nt(o)).replace(/aaa111/,a1.join(nt)) : o
}
var 循環=function(){
var arr候補, arr確定, key, arr重複候補
var getKey=function(){return key[LEN] ? '['+key.join('][')+']' : ''}
return {
init:function(){arr候補=[]; arr確定=[]; key=[]; arr重複候補=[], this.名前=''},
名前:'',
名前push:function(o){
this.名前 && key.push(fStr(this.名前))
// 追加した名前に対応するoと一致するものがarr候補にあるならtrueを返す
var fun=function(i,候補){return o==候補.o && arr確定.push({s:候補.key, d:getKey()}) && key.pop()}
if(key[LEN] && (forIn(arr候補,fun) || forIn(arr重複候補,fun))){return true}
// 一致するものがなければ候補に追加する
arr候補.push({o:o, key:getKey()})
},
pop:function(){key.pop(), arr重複候補.push(arr候補.pop())},
getRes:function(objName){var a=[]; for0L(arr確定,function(i,o){ a.push(objName+o.d+' = '+objName+o.s) }); return a}
}
}()
return function(o){
循環.init()
var s=main(o), a=循環.getRes('o')
return !a[LEN] ? s : 'function(){\n\tvar o='+s.replace(rNg,nt)+'\n\t'+a.join('\n\t')+'\n\treturn o\n}()'
}
}()
}
汎用スクリプト()
外部スクリプトファイル.デバッグ方法のサンプル()
ちなみにintWindowStyleに渡す値については以下を参照ください。
Microsoft Developer Network - WSH Runメソッド

0 件のコメント:

コメントを投稿