自動解析 github倉庫的目錄列表

清夜發表於2019-03-02

我在 github上有個維護時間比較長的 repository,開始時只有幾個檔案,後來檔案數目逐漸增多,期間整理了好幾次,現在已經整理成了好幾個資料夾了,有時候想找某個檔案的時候,但是不確定到底在哪個資料夾裡面,於是就憑感覺一個一個資料夾試過去,層級少點還好,但是層級一多,就算是明確知道在哪個資料夾裡,一層層點進去也要點好幾次

於是心中一動,就想著把當前倉庫的目錄結構列出來,直接寫在 README.md檔案上,想看哪個檔案直接點,一次點選即可,手寫目錄肯定是不太友好的,因為我可能頻繁增刪檔案,甚至是再次整理檔案結構,而且也不具備通用性,萬一哪天又想把另外一個倉庫也列出目錄結構,那麼又要手寫一遍,所以最好寫個程式碼程式來幫我完成這種工作

先看效果圖:

自動解析 github倉庫的目錄列表

遞迴獲取所有檔案路徑

目標是輸出目錄的層級結構,那麼首先要把當前倉庫根目錄下所有檔案的路徑獲取到,思路很清晰的,先用 fs.readdirSync讀取目錄,並且遞迴迴圈子目錄,直到最後一層

function getDirStruct(basePath = __dirname) {
  const files = fs.readdirSync(basePath)
  files.forEach(file => {
    // 處理先不要顯示的檔案
    if (excludeFile.indexOf(file) !== -1 || excludePrefix.some(pre => file.indexOf(pre) === 0)) return
    const fullPath = path.resolve(basePath, file)
    const fileStats = fs.statSync(fullPath)
    // 如果是資料夾,則繼續遍歷其子檔案
    return fileStats.isDirectory(file) ? getDirStruct(fullPath) : absolutePath.push(fullPath)
  })
}
複製程式碼

這裡獲取到的是所有檔案在本地目錄的絕對全路徑,但是後面是需要把這個東西上傳到 github的,所以需要把這個絕對路徑改為相對路徑,用於拼接檔案的 url地址

// 絕對路徑轉相對路徑
const rPath = path.relative(__dirname, apath)
複製程式碼

這裡有幾個小點需要注意下

  • 排除無用的干擾檔案

程式不可能直接執行在 github頁面上的,所以你需要把倉庫下載下來,再本地目錄中執行程式,那麼因為使用了 git的緣故,所以根目錄中肯定存在一個 .git資料夾,這個資料夾裡的東西很多,而且你也不太可能希望展示這個東西,所以最好排除掉

類似的還有一些 img資料夾,裡面存了很多圖片,你可能也不想展示出來,因為太佔篇幅了而且也沒什麼用,所以也要排除掉

  • 不同平臺的路徑分隔符

不同平臺上的檔案路徑分隔符是不一樣的,在 windows上 路徑分隔符是 ,而在 POSIX(即類 UNIX系統,包括 MacLinux)上則是 /,所以需要區別處理

nodejs中可通過 path.sep來獲取當前作業系統的檔案路徑分隔符

  • 遞迴

檔案的逐級讀取涉及到遞迴操作,如果目錄層級不是深到令人智熄的地步,那麼除了程式執行時間比較長以外,不會有什麼問題,但是如果是讀取類似於 node_modules這種資料夾,而且巢狀很深,那麼就可能導致 棧溢位,程式直接 boom

那麼這裡就不得不提到 尾遞迴了,尾遞迴就能很好地避免 棧溢位問題,關於 尾遞迴,參見 知乎:什麼是尾遞迴?

目錄樹(層級結構)

獲取到了所有檔案路徑之後,需要對這些檔案路徑按照進行整理,得到一棵 Dir Tree,就是一個用於描述這些檔案路徑的層級結構的資料

這裡構建的 Dir Tree類似下述結構:

{
  _children: [`README.md`, `LICENSE`],
  `CSS`: {
    _children: [`CSS-Note-1.md`, `CSS-Note-2.md`, `效能優化.md`],
  },
  `Vue`: {
    _children: [`效能優化.md`, `新特性.md`],
    `無渲染Vue元件`: {
      ...
    }
  }
}
複製程式碼

每個目錄層級下,對於單檔案,直接存入這個層級下的一個名為 _children的陣列屬性中,對於資料夾,則將資料夾的名字作為這個層級下一個屬性名,然後這個屬性的值,再按照上述規則進行遞迴,直到最後一層

當然,這個 Dir Tree的結構仁者見仁智者見智,只要你覺得順眼怎麼樣都可以,這裡只是舉了一個栗子◎

資料結構確定了,下面就需要將檔案路徑陣列整理成上面的結構,例如,對於 /project/demo/src/index.js這個路徑,需要整理成:

const tree = {
  project: {
    demo: {
      src: {
        _children: [`index.js`]
      }
    }
  }
}
複製程式碼

那麼這裡就有個問題了,在建立這個 tree物件之前,tree這個資料可能是沒有 project這個屬性的,又或者有 project屬性,但是這個屬性下沒有 demo這個屬性

解決的方法,很明顯的一個就是逐級判斷,沒有這個屬性的,就加上去,然後當構造出 tree.project.demo.src結構的時候,再在這個結構上,加上 ._children = [`index.js`]結構,這個過程其實可以簡化一下,比如藉助 超程式設計

例如,你要是覺得不要每次都要判斷到底有沒有這個屬性,那麼就可以使用 es6 Proxy,實現自動新增屬性的能力:

function autoAddProperty() {
  return new Proxy({}, {
    get(target, key, receiver) {
      if (!(key in target)) {
          target[key] = autoAddProperty()
        }
        return Reflect.get(target, key, receiver)
    }
  })
}
// 用法
const obj = autoAddProperty()
obj.a.b.c.d = 1
console.log(obj.a.b.c.d)  // => 1
複製程式碼

當然,除了 proxy之外,也可以藉助 eval,本示例使用的就是 eval,因為程式碼更簡潔,eval 這個方法可能很多 JavaScript書籍上都會提到不要隨便用,對於新手來說,這個特性坑比較多,所以在不明確其副作用的情況下,還是最好不要用,但並不是說不能用,如果用這個特性多寫一行程式碼就能另外少寫十行,那為什麼不用一用?

更多關於超程式設計的內容,可見 知乎: 怎麼理解超程式設計?【資源集合】 ES6 超程式設計(Proxy & Reflect & Symbol)

輸出結構

資料結構搞定了,那麼最後的輸出就很簡單了,就是按照層級進行解構,同樣是需要用到尾遞迴,值得稍微提一下的就是,想讓輸出的目錄結構呈現出一種次序關係,那麼就需要在遞迴中記住層級關係,可通過指定一個引數 level來實現,根據這個引數的值來決定製表符 的數量,從而控制縮排來表現層級

function formatLink(obj = structs, basePath = ``, level = 1) {
  // ...
}
複製程式碼

另外,給大家說個小 tipgithub頁面上是可以使用快捷鍵的,例如在一個 github倉庫頁面上按下 t鍵,就會啟用查詢檔案模式,不同的頁面,例如賬戶個人主頁和某個倉庫頁面可用的快捷鍵可能所有差別,而且這些快捷鍵很多,想要憑記憶記下來可能有些困難,可以按下 shift + /鍵,即可在當前 github頁面彈出一個 modal彈窗,上面就顯示當前頁面所有可用的快捷鍵

自動解析 github倉庫的目錄列表

總結

本文示例程式碼已經放到 github上了,嗯,這個 github倉庫下 README.md上展示的目錄層級結構,就是根據這份檔案生成的

相關文章