前言
這段時間空余時間蠻少,后來特地騰出晚上的時間來開發(fā)自己的玩具拔创。 今天講的是為玩具所開發(fā)的一個小模塊的一個功能利诺。 具體來說它是一個仿Tree命令能夠羅列給定目錄的樹形結(jié)構(gòu)。而且我的做法是開發(fā)一個命令行工具剩燥,但這里先不提慢逾,我們就專注這個目錄列表功能。 需要輸出相同的結(jié)構(gòu)。我們先來看下需要達(dá)到的效果
正文
我們先來探討一下如何獲得目錄結(jié)構(gòu)侣滩。因為我們最終需要的是給路徑->獲得目錄結(jié)構(gòu)->數(shù)據(jù)處理(輸出/另外操作)
在Node里有這么幾個API
fs.readdir(path, callback)
該方法是 readdir(3) 的異步執(zhí)行版本口注,用于讀取一個目錄的內(nèi)容。callback 接收兩個參數(shù) (err, files)君珠,其中 files 是一個數(shù)組寝志,數(shù)組成員為當(dāng)前目錄下的文件名,不包含 . 和 ..策添。
fs.readdirSync(path)
該方法是 readdir(3) 的同步執(zhí)行版本澈段,返回一個不包含 . 和 .. 的文件名數(shù)組。
...這里就列兩個舰攒,其他的請自行去官網(wǎng)查API0-0
一開始我試圖用異步方法來獲取败富,但是最后因為沒處理好Promise和其它異步操作,導(dǎo)致最后數(shù)據(jù)沒有存儲下來摩窃。因此現(xiàn)在我們講的是用同步方式獲取兽叮。異步方式也將在這個模塊寫完之后再去寫一版本。
基本上網(wǎng)上的方法都是遞歸調(diào)用方法來獲取整個目錄結(jié)構(gòu)猾愿。
這里也不例外鹦聪。不過我們需要注意我們需要的將整個目錄關(guān)系都緩存在一個Object里。而且因為我需要指定層級目錄輸出蒂秘。因此每個文件/目錄還將有一個屬性就是deep泽本,它作為一個告訴調(diào)用者該目錄層級的深度。如果是0姻僧,說明是指定目錄的根目錄下同級的文件/目錄规丽。依次類推。
當(dāng)然這里就直接貼出代碼
_getAllNames: function(level, dir) {
? ? ? ? var filesNameArr = []
? ? ? ? let cur = 0
? ? ? ? ? ? // 用個hash隊列保存每個目錄的深度
? ? ? ? var mapDeep = {}
? ? ? ? mapDeep[dir] = 0
? ? ? ? ? ? // 先遍歷一遍給其建立深度索引
? ? ? ? function getMap(dir, curIndex) {
? ? ? ? ? ? var files = fs.readdirSync(dir) //同步拿到文件目錄下的所有文件名
? ? ? ? ? ? files.map(function(file) {
? ? ? ? ? ? ? ? //var subPath = path.resolve(dir, file) //拼接為絕對路徑
? ? ? ? ? ? ? ? var subPath = path.join(dir, file) //拼接為相對路徑
? ? ? ? ? ? ? ? var stats = fs.statSync(subPath) //拿到文件信息對象
? ? ? ? ? ? ? ? ? ? // 必須過濾掉node_modules文件夾
? ? ? ? ? ? ? ? if (file != 'node_modules') {
? ? ? ? ? ? ? ? ? ? mapDeep[file] = curIndex + 1
? ? ? ? ? ? ? ? ? ? if (stats.isDirectory()) { //判斷是否為文件夾類型
? ? ? ? ? ? ? ? ? ? ? ? return getMap(subPath, mapDeep[file]) //遞歸讀取文件夾
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? //console.log(subPath)
? ? ? ? ? ? })
? ? ? ? }
? ? ? ? getMap(dir, mapDeep[dir])
? ? ? ? ? ? //console.log(mapDeep)
? ? ? ? function readdirs(dir, folderName,myroot) {
? ? ? ? ? ? var result = { //構(gòu)造文件夾數(shù)據(jù)
? ? ? ? ? ? ? ? path: dir,
? ? ? ? ? ? ? ? name: path.basename(path),
? ? ? ? ? ? ? ? type: 'directory',
? ? ? ? ? ? ? ? deep: mapDeep[folderName]
? ? ? ? ? ? }
? ? ? ? ? ? var files = fs.readdirSync(dir) //同步拿到文件目錄下的所有文件名
? ? ? ? ? ? result.children = files.map(function(file) {
? ? ? ? ? ? ? ? //var subPath = path.resolve(dir, file) //拼接為絕對路徑
? ? ? ? ? ? ? ? var subPath = path.join(dir, file) //拼接為相對路徑
? ? ? ? ? ? ? ? var stats = fs.statSync(subPath) //拿到文件信息對象
? ? ? ? ? ? ? ? ? ? //console.log(subPath)
? ? ? ? ? ? ? ? if (stats.isDirectory()) { //判斷是否為文件夾類型
? ? ? ? ? ? ? ? ? ? // console.log(mapDeep[file])
? ? ? ? ? ? ? ? ? ? return readdirs(subPath, file,file) //遞歸讀取文件夾
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? return { //構(gòu)造文件數(shù)據(jù)
? ? ? ? ? ? ? ? ? ? path: subPath,
? ? ? ? ? ? ? ? ? ? name: file,
? ? ? ? ? ? ? ? ? ? type: 'file'
? ? ? ? ? ? ? ? }
? ? ? ? ? ? })
? ? ? ? ? ? return result //返回數(shù)據(jù)
? ? ? ? }
? ? ? ? filesNameArr.push(readdirs(dir, dir))
? ? ? ? return filesNameArr
? ? },
過濾掉node_modules是必須的撇贺,因為開發(fā)中這個目錄一直存在赌莺。。松嘶。是個極大干擾源艘狭。
這里我代碼都加了注釋應(yīng)該很好理解。
我們來輸出下緩存的目錄結(jié)構(gòu)
{ path: './',
? name: '[object Object]',
? type: 'directory',
? deep: 0,
? children:
? [ { path: '.DS_Store', name: '.DS_Store', type: 'file' },
? ? { path: '.babelrc', name: '.babelrc', type: 'file' },
? ? { path: '.gitignore', name: '.gitignore', type: 'file' },
? ? { path: 'README.MD', name: 'README.MD', type: 'file' },
? ? { path: 'bin',
? ? ? name: '[object Object]',
? ? ? type: 'directory',
? ? ? deep: 1,
? ? ? children: [Object] },
? ? { path: 'lib',
? ? ? name: '[object Object]',
? ? ? type: 'directory',
? ? ? deep: 1,
? ? ? children: [Object] },
? ? { path: 'package.json', name: 'package.json', type: 'file' },
? ? { path: 'test',
? ? ? name: '[object Object]',
? ? ? type: 'directory',
? ? ? deep: 1,
? ? ? children: [Object] } ] }
[ { path: './',
? ? name: '[object Object]',
? ? type: 'directory',
? ? deep: 0,
? ? children:
? ? [ [Object],
? ? ? [Object],
? ? ? [Object],
? ? ? [Object],
? ? ? [Object],
? ? ? [Object],
? ? ? [Object],
? ? ? [Object] ] } ]
針對./這個當(dāng)前目錄的路徑翠订,程序返回上面的結(jié)構(gòu)巢音。 這樣我們就得到了第一步最基礎(chǔ)的數(shù)據(jù)。
現(xiàn)在第一個步驟是為了模仿Tree工具輸出命令尽超。
這里給個例子參考:
├── .DS_Store
├── .babelrc
├── .gitignore
├── README.MD
├── bin
│?? ├── .DS_Store
│?? ├── folderTree.js
│?? ├── lib2
│?? │?? ├── .DS_Store
│?? │?? └── testa
│?? └── testb
│ ?? ? └── .DS_Store
├── lib
│?? ├── .DS_Store
│?? ├── folderFactory.js
│?? └── testlib
│ ?? ? └── testlibfile.js
├── package.json
└── test
?? ├── .DS_Store
?? ├── index.js
? ? └── testFolder
??? ?? ├── .DS_Store
??? ?? ├── a
??? ?? ├── b
? ?? ? └── c
它有幾個注意點官撼,一個是每當(dāng)你的文件或者目錄是當(dāng)前層級最后一個,那么輸出└──前綴如果是普通的輸出├──?前綴橙弱。 如果是多層級歧寺,那么最前面依舊需要│?來進(jìn)行上下層級的聯(lián)系燥狰。
而且還有很多小細(xì)節(jié)需要處理棘脐。
一開始最好的解決方法其實是遞歸斜筐。但是我覺得有點復(fù)雜,我想試下迭代方法蛀缝。最后結(jié)果如開頭的效果顷链。基本上如果根目錄中最后一個file是目錄類型屈梁。那么它是正常的嗤练。但是如果根目錄中最后一個文件是文件類型類型。我這段迭代并沒有進(jìn)行處理在讶。
我們先來看下這個有缺陷的代碼:
? ? var stack = [obj[0]]
? ? ? ? var isFinal = false
? ? ? ? function printFolder(arr, folderName, isLastFolder) {
? ? ? ? ? ? if (arr.deep <= level) {
? ? ? ? ? ? ? ? for (var i = 0; i < arr.children.length; i++) {
? ? ? ? ? ? ? ? ? ? var isLastFile = i == arr.children.length - 1 ? true : false
? ? ? ? ? ? ? ? ? ? var isRootLast = i == arr.children.length - 1
? ? ? ? ? ? ? ? ? ? isFinal = isFinal == true ? true :? arr.deep == 0 && arr.children[i].type == 'directory' && i == arr.children.length-1
? ? ? ? ? ? ? ? ? ? printFile(arr.children[i], folderName, arr.deep, isLastFile, isLastFolder,isFinal)
? ? ? ? ? ? ? ? ? ? if (arr.children[i].type == 'directory') {
? ? ? ? ? ? ? ? ? ? ? ? //console.log('directory')
? ? ? ? ? ? ? ? ? ? ? ? var t = arr.children[i].path
? ? ? ? ? ? ? ? ? ? ? ? if (i == arr.children.length - 1) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? printFolder(arr.children[i], t, true)
? ? ? ? ? ? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ? ? ? ? ? ? printFolder(arr.children[i], t, false)
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? function printFile(file, folderName, deep, isLastFile, isLastFolder) {
? ? ? ? ? ? if (file[0] != '.') {
? ? ? ? ? ? ? ? // console.log("Folder:"+folderName)
? ? ? ? ? ? ? ? // console.log(deep)
? ? ? ? ? ? ? ? //console.log(isLastFile)
? ? ? ? ? ? ? ? //console.log(isLastFolder)
? ? ? ? ? ? ? ? var name = file.path.replace(folderName + '/', '')
? ? ? ? ? ? ? ? //console.log(isFinal)
? ? ? ? ? ? ? ? if (deep == 0) {
? ? ? ? ? ? ? ? ? ? if (isLastFile) {
? ? ? ? ? ? ? ? ? ? ? ? console.log('└── ' + name)
? ? ? ? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ? ? ? ? console.log('├── ' + name)
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? if (deep > 0) {
? ? ? ? ? ? ? ? ? ? if (!isLastFolder) {
? ? ? ? ? ? ? ? ? ? ? ? if (!isLastFile) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? console.log('│?? '.repeat(deep) + '├── ' + name)
? ? ? ? ? ? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ? ? ? ? ? ? console.log('│?? '.repeat(deep) + '└── ' + name)
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ? ? ? ? if (!isLastFile) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? console.log(' ?? '.repeat(deep) + '├── ' + name)
? ? ? ? ? ? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ? ? ? ? if(isFinal){
? ? ? ? ? ? ? ? ? ? ? ? ? ? console.log('? ? '.repeat(deep) + '└── ' + name)
? ? ? ? ? ? ? ? ? ? ? ? }else{
? ? ? ? ? ? ? ? ? ? ? ? ? var str = '? ? '.repeat(deep) + '└── ' + name
? ? ? ? ? ? ? ? ? ? ? ? ? var temp = str.split('')
? ? ? ? ? ? ? ? ? ? ? ? ? temp[0] = '│'
? ? ? ? ? ? ? ? ? ? ? ? ? ? console.log(temp.join(''))
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? printFolder(obj[0], '', false)
? ? ? ? console.log('目錄及文件羅列完畢')
我做了很多判斷煞抬,但還是低估了一個目錄它可能的復(fù)雜性(目錄中有空目錄,目錄中有空文件构哺,文件和目錄的多個嵌套等等)革答。因為我開發(fā)的時候是根據(jù)幾個example來進(jìn)行調(diào)節(jié)判斷。當(dāng)我最后寫完曙强,重新再去測試幾個目錄的時候發(fā)現(xiàn)出現(xiàn)上述的file缺陷残拐。 如圖:?
因此需要改寫為遞歸方式。我們不可能根據(jù)別的屬性來進(jìn)行判斷碟嘴。因此我們需要依賴的還是之前的那份目錄結(jié)構(gòu)緩存溪食。
這里也直接貼源碼:
_showList(obj, level) {
? ? ? ? var sourceStruct = obj[0]
? ? ? ? var dirCount = 0
? ? ? ? var fileCount = 0
? ? ? ? ? ? // 字符集
? ? ? ? var charSet = {
? ? ? ? ? ? 'node': '├── ', //節(jié)點
? ? ? ? ? ? 'pipe': '│? ', // 上下鏈接
? ? ? ? ? ? 'last': '└── ', // 最后的file或folder需要回勾
? ? ? ? ? ? 'indent': '? ? ' // 縮進(jìn)
? ? ? ? };
? ? ? ? function log(file, depth, parentHasNextSibling) {
? ? ? ? ? // console.log("log:")
? ? ? ? ? ? if (!parentHasNextSibling && depth.length > 1) {
? ? ? ? ? ? ? ? // Replace a pipe with an indent if the parent does not have a next sibling.
? ? ? ? ? ? ? ? depth[depth.length - 2] = charSet.indent;
? ? ? ? ? ? }
? ? ? ? ? ? if(file.lastIndexOf('/') > -1){
? ? ? ? ? ? ? ? file = file.substring(file.lastIndexOf('/')+1)
? ? ? ? ? ? }
? ? ? ? ? ? console.log(depth.join('') + file);
? ? ? ? }
? ? ? ? // 由于已經(jīng)有緩存數(shù)據(jù)了,因此對數(shù)據(jù)進(jìn)行遍歷搜索
? ? ? ? // 如果type 是file 就不需要繼續(xù)
? ? ? ? // 如果type 是directory 對臨時數(shù)組
? ? ? ? function walk(path, depth, parentHasNextSibling) {
? ? ? ? ? //? console.log(path)
? ? ? ? ? ? var childrenLen = path.length - 1
? ? ? ? ? // console.log(childrenLen)
? ? ? ? ? ? var loop = true
? ? ? ? ? ? if (depth.length >= level) {
? ? ? ? ? ? ? ? loop = false
? ? ? ? ? ? }
? ? ? ? ? ? if (loop) {
? ? ? ? ? ? ? ? path.forEach(function walkChildren(child, index) {
? ? ? ? ? ? ? ? ? ? var newdepth = !!depth ? depth.slice(0) : []
? ? ? ? ? ? ? ? ? ? var isLast = (index >= childrenLen)
? ? ? ? ? ? ? ? ? ? if (isLast) {
? ? ? ? ? ? ? ? ? ? ? ? newdepth.push(charSet.last)
? ? ? ? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ? ? ? ? newdepth.push(charSet.node)
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? if(child.type == "file"){
? ? ? ? ? ? ? ? ? ? ? log(child.name, newdepth, parentHasNextSibling)
? ? ? ? ? ? ? ? ? ? }else{
? ? ? ? ? ? ? ? ? ? ? log(child.path, newdepth, parentHasNextSibling)
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? if (child.type == "directory") {
? ? ? ? ? ? ? ? ? ? ? ? var childPath = child.children
? ? ? ? ? ? ? ? ? ? ? ? if (!isLast) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? newdepth.pop()
? ? ? ? ? ? ? ? ? ? ? ? ? ? newdepth.push(charSet.pipe)
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? walk(childPath, newdepth, !isLast)
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? })
? ? ? ? ? ? ? ? loop = !loop
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? walk(sourceStruct.children, [])
? ? ? ? //console.log(sourceStruct)
? ? ? ? console.log('level:' + level)
? ? ? ? console.log('目錄及文件羅列完畢')
? ? },
這里只需要判斷文件類型娜扇,利用遞歸的特性將每個文件的路徑存于newpath错沃,然后判斷長度,如果大于level就跳過雀瓢。具體注釋都在源碼里了捎废。
結(jié)尾
第一個模塊總算完成,四月第一篇文章居然在中旬也算對得起拖延癥了~