[Tablacus Explorer] 現在or選択したディレクトリ以下のファイル・フォルダのパスをクリップボードにコピー

Tablacusで現在または選択したディレクトリ以下のファイルツリーをJSON形式で取得して クリップボードにコピーするTablacus用のJScript

使い方

JScriptを実行するとプロンプトのウィンドウが出てくるので出力オプションを指定する。

  • /: パス区切りを "/" に変換
  • f: ファイルを列挙
  • d: ディレクトリを列挙
  • r: 再帰的な構造を生成
  • {数値}: 探索するフォルダ階層(規定は0)
  • 最初の":"以降の文字列: ファイル名フィルタ(正規表現に一致するファイルのみ取得)
  • 2番目の":"以降の文字列: フォルダ名フィルタ(正規表現に一致するフォルダのみ取得)'

オプション指定と結果の例

ディレクトリ構成

dir0
│  0.htm
│  0.txt
│
└─dir1
    │  1.htm
    │  1.txt
    │
    └─dir2
        │  2.htm
        │  2.txt
        │
        └─dir3
                3.htm
                3.txt

オプション: /f1:\.html?$ 結果:

[
  "C:/dir0/0.htm",
  "C:/dir0/dir1/1.htm"
]

オプション: d99::dir[12]
結果:

[
  "C:\\dir0",
  "C:\\dir0\\dir1",
  "C:\\dir0\\dir1\\dir2"
]

オプション: d99::dir[23] 結果: dir1が弾かれた結果、ルートディレクトリだけ出力される

[
  "C:\\dir0"
]

オプション: /fdr1:\.txt 結果: false付いてるのは{数値}のオプションの深度上限に達したので探索中止した。

{
  "directories": {
    "C:/dir0": {
      "files": [
        "0.txt"
      ],
      "directories": {
        "dir1": {
          "files": [
            "1.txt"
          ],
          "directories": {
            "dir2": false
          }
        }
      }
    }
  }
}

JScriptのソース

動作には問題ないと思うけど、再利用する気の起きないクソコードになってしまった…

/**
 * 指定したディレクトリ以下のオブジェクト名を取得する関数
 * @param {string} targetDir 探索するルートディレクトリ
 * @param {integer} depth 探索するサブディレクトリの深さ(正の整数)
 *     0 を指定した場合は targetDir のみ
 *     1 を指定した場合は targetDir のサブディレクトリの中まで
 *     n を指定した場合は察して下さい。
 * @param {function} fileFilter = (fileName) -> bool
 *     ファイル名をこれに食わせて false が返ってきたらそのファイルは無視される
 * @param {function} directoryFilter = (directoryName) -> bool
 *     ディレクトリ名をこれに食わせて false が返ってきたらそのファイルは無視される
 * @param {object} _dest 再帰呼出で使うため指定しないこと
 * @param {_currentDepth} 再帰呼出で使うため指定しないこと
 * @return {object} 下のような構造のオブジェクト
 *   {
 *     files: [ファイルパスの配列],
 *     directories: {
 *       "ディレクトリ名1": 同様の構造のオブジェクト, 
 *       "ディレクトリ名n": 同様の構造のオブジェクト,
 *     }
 *   }
 */
var GetFolderTree = function(
    targetDir,
    depth,
    fileFilter,
    directoryFilter,
    _dest,
    _currentDepth) {
  targetDir = targetDir.replace(/\\+$/, '');
  if ( ! fso.FolderExists(targetDir)) return null;
  
  fileFilter = fileFilter || function() { return true; }
  directoryFilter = directoryFilter || function() { return true; }

  var node = {};
  _currentDepth = _currentDepth !== void 0 ? _currentDepth : 0;
  if (depth !== -1 && depth < _currentDepth) {
    node = false;
  }

  var dirName;
  if (_dest) {
    _dest.directories || (_dest.directories = {});
    dirName = fso.GetFileName(targetDir);
  } else {
    _dest = { directories: {} };
    dirName = targetDir;
  }
  _dest.directories[dirName] = node;
  
  if (node === false) return _dest;

  targetDir = targetDir.replace(/\\$/, '');
  var folder = fso.GetFolder(targetDir);

  var e = new Enumerator(folder.Files);
  for ( ; !e.atEnd(); e.moveNext()) {
    var file = e.item().Name;
    if (fileFilter(file)) {
      node.files || (node.files = []);
      node.files.push(file);
    }
  }

  e = new Enumerator(folder.SubFolders);
  for ( ; !e.atEnd(); e.moveNext()) {
    var dir = e.item().Name;
    if (directoryFilter(dir)) {
      GetFolderTree(
        targetDir + '\\' + dir,
        depth,
        fileFilter,
        directoryFilter,
        node,
        _currentDepth + 1);
    }
  }
  
  var propExists = false;
  for (var x in node) {
    propExists = true;
    break;
  }
  if ( ! propExists) _dest.directories[dirName] = null;
  
  return _dest;
};

/**
 * GetFolderTree で取得したオブジェクトを探索
 * @param {object} tree GetFolderTreeの戻り値
 * @param {function} onFindFiles = (fileArray, node, currentDirFullPath)
 *   ファイル配列のコールバック。currentDirFillPathはnullのことあり(ルートノードの場合)
 * @param {function} onFindDirectoryName = (directoryName, node, currentDirFullPath)
 *   ディレクトリ名のコールバック。node[directoryName] で子ノード取得できる(nullのことあり)
 * @param _currentDirectory 再帰呼出で利用するので指定しないこと
 */
var WalkTree = function(tree, onFindFiles, onFindDirectoryName, _currentDirectory) {
  if (!tree) return;
  onFindFiles = onFindFiles || function(){};
  onFindDirectoryName = onFindDirectoryName || function(){};
  
  if (tree.files && tree.files.length) {
    onFindFiles(tree.files, tree, _currentDirectory);
  }
  
  if (tree.directories) {
    for (var dirName in tree.directories) {
      var fullDir = (_currentDirectory ? _currentDirectory + '\\' : '') + dirName;
      onFindDirectoryName(dirName, tree, fullDir);
      
      var node = tree.directories && tree.directories[dirName];
      if (node) {
        WalkTree(node, onFindFiles, onFindDirectoryName, fullDir); 
      }
    }
  }
};

/**
 * GetFolderTree で取得したオブジェクトから
 * 空のdirectoriesを取り除く
 * @param {object} tree GetFolderTreeの戻り値
 */
var RemoveEmptyDirectoryFromTree = function(tree) {
  WalkTree(tree, null, function onDirName(dirName, node) {
    if (node.directories) {
      var dirs = node.directories;
      if ( ! dirs[dirName]) {
        delete dirs[dirName];
      }
      
      var isEmpty = true;
      for (var x in dirs) {
        isEmpty = false;
        break;
      }
      if (isEmpty) delete node.directories;
    }
  });
};

/**
 * コマンドライン引数を配列にsplit
 * @param {String} text コマンドライン文字列
 * @param {Char} _delimChar 区切り文字1文字(
 * @retrun {Array} 配列。nullは返さない。
 */
var SplitCommandLineArguments = function(text, _delimChar) {
  var ret = [], delim =  (_delimChar || ' ').charCodeAt(0), i = 0, c, quoteContext = false, fromPos = -1;
  while(c = text[i]) {
    c = c.charCodeAt(0);
    if (quoteContext) {
      if (c === 34) {
        ret.push(text.substring(fromPos, i));
        fromPos = -1;
        quoteContext = false;
      }
    } else {
      if (fromPos === -1) {
        if (c === 34) {
          quoteContext = true;
          fromPos = i + 1;
        } else if (c !== delim) {
          fromPos = i;
        }
      } else {
        if (c === delim) {
          ret.push(text.substring(fromPos, i));
          fromPos = -1;
        }
      }
    }
    i++;
  }
  
  if (fromPos !== -1) {
    text = text.substr(fromPos);
    for(i = text.length - 1; i >= 0; i--) {
      if (text[i].charCodeAt(0) !== delim) {
        ret.push(text.substr(0, i + 1));
        break;
      }
    }
  }
  
  return ret;
};

//
// tablacus用の処理ここから
//
var description = [
  '[ファイル一覧をクリップボードにコピー]',
  'オプション指定方法(例: "/fr2:\.html?$"):',
  '/: パス区切りを"/"に変換',
  'f: ファイルを列挙',
  'd: ディレクトリを列挙',
  'r: 再帰的な構造を生成',
  '{数値}: 探索するフォルダ階層(規定は0)',
  '最初の":"以降の文字列: ファイル名フィルタ(正規表現に一致するファイルのみ取得)',
  '2番目の":"以降の文字列: フォルダ名フィルタ(正規表現に一致するフォルダのみ取得)'
];

var options = (prompt(description.join(' '), '/f') || '').split(':');
var filterOptions = options.slice(1);
var options = options[0];

var i;

/* fileFilter, dirFilter の生成 */
var fileFilter;
var dirFilter;
!function(){
  var fileRegex = filterOptions[0] && new RegExp(filterOptions[0], 'i');  
  if (options.indexOf('f') === -1) {
    fileFilter = function(){ return false; };
  } else if (fileRegex) {
    fileFilter = function(fileName) { return fileRegex.test(fileName); };
  } else {
    fileFilter = function(fileName) { return true; };
  }

  var dirRegex = filterOptions[1] && new RegExp(filterOptions[1], 'i');
  if (dirRegex) {
    dirFilter = function(dirName) { return dirRegex.test(dirName); };
  } else {
    dirFilter = function(dirName) { return true; };
  }
}();

/* ディレクトリ探索 */
var tree = (function() {
  var depth = options.match(/\d+/);
  depth = depth ? depth[0] * 1 : 0;

  var currentDir = eventTE.Environment.current();
  var selectedItems = SplitCommandLineArguments(eventTE.Environment.selected());
  if (selectedItems[0]) {
    var tree = { directories: {} };
    var node = {};
    tree.directories[currentDir] = node;

    var path;
    for (i = 0; path = selectedItems[i]; i++) {
      path = path.replace(/^"/, '').replace(/"$/, '');
      var name = fso.GetFileName(path);
      if (fso.FileExists(path)) {
        if (fileFilter(name)) {
          node.files || (node.files = []);
          node.files.push(name);
        }
      } else {
        if (dirFilter(name)) {
          GetFolderTree(path, depth, fileFilter, dirFilter, node, 1);
        }
      }
    }
    return tree;
  } else {
    return GetFolderTree(
      currentDir,
      depth,
      fileFilter,
      dirFilter);
  }
})();

/* dオプションなしの場合は tree から空のディレクトリのプロパティを除去 */
var needsDir = options.indexOf('d') !== -1;
if ( ! needsDir) {
  RemoveEmptyDirectoryFromTree(tree);
}

var ret;
if (options.indexOf('r') !== -1) {
  /* r オプションありはそのまま */
  ret = tree;
} else {
  /* r オプションがない場合は単純な配列に変換 */
  ret = [];
  WalkTree(tree, function(files, node, dirFullPath) {
      if (dirFullPath) {
        for (i = 0; files[i]; i++) {
          ret.push(dirFullPath + '\\' + files[i]);
        }
      }
    }, function(dirName, node, dirFullPath) {
      if (needsDir) {
        ret.push(dirFullPath);
      }
    });
}

/* クリップボードに貼り付けて終了 */
var data = JSON.stringify(ret, null, '\t');
if (options.indexOf('/') !== -1) {
  /* ファイル名には "\" が使えないので雑な変換で問題なし */
  data = data.replace(/\\\\/g, '/');
}
clipboardData.setData("text", data);
Share
関連記事