Saturday, October 17, 2020 / ExtendScript, JavaScript, Recursion, functional-programming

ExtendScript で 指定したフォルダ以下のフォルダ全部を探すには その1

指定したフォルダ以下のサブフォルダ、サブサブフォルダ...を含めてすべてのフォルダを探す、というコードを書いてみた。 ExtendScript では each や map, reduce などが使えないので、実装は少し面倒でした。

基本はこれ

以前のエントリー 再帰を使って木構造をリストに変換する JavaScript の このコード:

const toNodeList = (node)=> {
    if( node.children.length==0 ){
        return [node];
    }

    return node.children.reduce( (a,b)=> a.concat( toNodeList(b) ), [node]);
};

を実装すればいいだけのはず。 ここでは node になっているがこれが folder に代わるだけの話だ。

reduce を用意

ExtendScript では reduce がないので、はじめに reduce 関数を用意。

// 先頭の要素を返す.
var head = function(list){
    return list[0];
}

// 先頭の要素を取り除いた残りを返す.
var tail = function(list){
    var len = list.length;
    if( len==1 ){
        return [];
    }
  
    var newList = [];
    for(var i=0; i<(len-1); i++){
        newList.push( list[(i+1)] );
    }
    return newList;
};

var reduce = function(f, accumulator, list){
    if(list.length == 0){
        return accumulator;
    }

    return reduce(
        f,
        f(accumulator, head(list)),
        tail(list));
};

reduce 関数の作動テスト

reduce 関数の作動を確かめるために、 1から10まで足す を reduce を使って実装します。

var total = reduce( function(a,b){ return a+b; }, 0, [1,2,3,4,5,6,7,8,9,10] );
$.writeln(total);// 55

これを VSCode + ExtendScript Debugger で実行すると 55 と出力されます。 問題なさそうです。

肝心の処理

それでは冒頭のコードを ExtendScript に移植:

// 再帰的に処理して全てのフォルダを見つけてリストにして返す.
var toFolderList = function(folder){
    var subFolders = children(folder);
    if( subFolders.length==0 ){
        return [folder];
    }

    var myFunc = function(a,b){
        return a.concat(toFolderList(b));//ここで再帰.
    };
    return reduce(myFunc, [folder], subFolders);
};

元は node.children.reduce( (a,b)=> a.concat( toNodeList(b) ), [node]) と書いていた部分が reduce(myFunc, [folder], subFolders)) です。 また ヘルパー関数として children を使っています。 これは folder 内にあるサブフォルダのリストを返す関数です。

var isFolder = function(fileOrFolder){
    return ( fileOrFolder.constructor.name == 'Folder' );
};

// このフォルダ内にあるフォルダリストを返す.
var children = function(folder){
    var files = folder.getFiles();
    var folderList = [];
    for(var i=0; i<files.length; i++){
        if( isFolder(files[i]) ){
            folderList.push(files[i]);
        }
    }
    return folderList;
};

これで準備はできたので、あとは、toFolderList を使うだけで、指定したフォルダ以下にあるすべてのフォルダのリストが得られます。

var folder = Folder('/path/to/targetFolder');
var allFolderList = toFolderList( folder );

応用: 特定フォルダ以下の すべての PNG ファイルを抽出

この関数を使って、具体的な処理を書いてみましょう。 使えば、 例えば、特定フォルダ以下にあるすべての png ファイルを探すのも簡単。

var isPngFile = function(fileOrFolder){
    return ( fileOrFolder.constructor.name == 'File' && fileOrFolder.name.match(/png$/));
};

var folder = Folder('/path/to/targetFolder');
var allFolderList = toFolderList( folder );

var pngFileList = [];
for(var i=0; i<allFolderList.length; i++){
    var files = allFolderList[i].getFiles();
    for(var j=0; j<files.length; j++){
        var fileOrFolder = files[j];
        if( isPngFile(fileOrFolder) ){
            pngFileList.push(fileOrFolder);
        }
    }
}

$.writeln(pngFileList);

ExtendScript には getFiles('*.png') のような表現が使えるのでこれを使えばもう少し簡単になります.

var pngFileList = [];
for(var i=0; i<allFolderList.length; i++){
    var files = allFolderList[i].getFiles();
    var pngFiles = allFolderList[i].getFiles('*.png');
    for(var j=0; j<pngFiles.length; j++){
        pngFileList.push( pngFiles[j] );
    }
}
$.writeln(pngFileList);

蛇足: シェルスクリプト使えば……

bash などを使えば、特定フォルダ以下の png 拡張子を持ったファイルリストなどは:

find /path/to/targetFolder -name "*.png"

の一行で済む話ではある。

ただ、ExtendScript で使うには、このテキストファイルを読んで処理しなければいけないし、 そもそも、ExtendScript 単体で完結させたい場合も多いので。

ExtendScript のみでもっと簡単に png ファイルを全部探すを実装する例を Extendscript で 指定したフォルダ以下のフォルダ全部を探すには その2 (もっと単純に) に書きました。