基于Go語言實現(xiàn)一個目錄樹打印工具
在日常開發(fā)中,我們經(jīng)常需要可視化項目的目錄結(jié)構。無論是編寫文檔、分享項目結(jié)構,還是單純了解代碼布局,一個清晰的目錄樹展示都至關重要。今天我將介紹一款用Go語言開發(fā)的目錄樹打印工具,它不僅能生成美觀的目錄結(jié)構圖,還提供多種實用功能!
功能亮點
多層級展示:支持自定義深度限制
隱藏文件處理:可選顯示隱藏文件/文件夾
多種輸出方式:控制臺打印、保存文件、復制到剪貼板
美觀可視化:使用emoji圖標標識不同類型
靈活連接線:可選的樹形連接線展示
智能排序:目錄優(yōu)先,按名稱排序
技術實現(xiàn)解析
核心數(shù)據(jù)結(jié)構
type DirectoryPrinter struct {
rootDir string // 根目錄路徑
showHidden bool // 是否顯示隱藏文件
currentDepth int // 當前遞歸深度
maxDepth int // 最大深度限制
indentSymbol string // 縮進符號(4個空格)
folderSymbol string // 文件夾圖標
fileSymbol string // 文件圖標
output []string // 輸出內(nèi)容收集
showConnector bool // 是否顯示連接線
}
關鍵算法邏輯
1.文件排序策略:
目錄優(yōu)先于文件
相同類型按名稱升序排列
sort.Slice(filteredEntries, func(i, j int) bool {
if filteredEntries[i].IsDir() != filteredEntries[j].IsDir() {
return filteredEntries[i].IsDir()
}
return filteredEntries[i].Name() < filteredEntries[j].Name()
})
2.連接線生成邏輯:
非最后一項:├──
最后一項:└──
if dp.showConnector {
isLast := index == total-1
connector := "├── "
if isLast {
connector = "└── "
}
return fmt.Sprintf("%s%s?? %s\n", prefix, connector, entry.Name())
}
3.遞歸深度控制:
if dp.maxDepth > 0 && dp.currentDepth >= dp.maxDepth {
return nil
}
使用指南
命令行參數(shù)
| 參數(shù) | 說明 | 示例 |
|---|---|---|
| --show-hidden | 顯示隱藏文件 | --show-hidden |
| --output-file | 保存到文件 | --output-file tree.txt |
| --copy-ClipBoard | 復制到剪貼板 | --copy-ClipBoard |
| --max-depth | 設置最大深度 | --max-depth 3 |
| --show-connector | 顯示連接線 | --show-connector |
| --help | 顯示幫助 | --help |
使用示例
基本用法(顯示當前目錄結(jié)構):
directory_printer
顯示隱藏文件并限制深度:
directory_printer --show-hidden --max-depth 2
保存到文件并顯示連接線:
directory_printer --output-file project_tree.txt --show-connector
輸出示例
?? my-project
?? src
?? controllers
?? models
?? views
?? config
?? public
?? css
?? js
?? images
?? README.md
?? .gitignore
?? go.mod
帶連接線版本:
?? my-project
├── ?? src
│ ├── ?? controllers
│ ├── ?? models
│ └── ?? views
├── ?? config
├── ?? public
│ ├── ?? css
│ ├── ?? js
│ └── ?? images
├── ?? README.md
├── ?? .gitignore
└── ?? go.mod
實現(xiàn)細節(jié)解析
根目錄處理
rootName := filepath.Base(rootDir)
if rootName == "." {
// 獲取當前工作目錄的絕對路徑
absPath, err := filepath.Abs(rootDir)
if err != nil {
rootName = "current_directory"
} else {
rootName = filepath.Base(absPath)
}
}
printer.output = append(printer.output, fmt.Sprintf("?? %s\n", rootName))
輸出處理邏輯
// 保存到文件
if *outputFile != "" {
err = saveToFile(*outputFile, printer.output)
}
// 復制到剪貼板
if *copyClipBoard {
content := strings.Join(printer.output, "")
err = clipboard.WriteAll(content)
}
???????// 控制臺輸出
fmt.Println("\n=== Directory Structure ===")
for _, line := range printer.output {
fmt.Print(line)
}遞歸目錄遍歷
childPrinter := &DirectoryPrinter{
rootDir: filepath.Join(dp.rootDir, entry.Name()),
showHidden: dp.showHidden,
currentDepth: dp.currentDepth + 1,
maxDepth: dp.maxDepth,
indentSymbol: dp.indentSymbol,
folderSymbol: dp.folderSymbol,
fileSymbol: dp.fileSymbol,
output: dp.output,
showConnector: dp.showConnector,
}
if err := childPrinter.printDirectory(); err != nil {
return err
}
dp.output = childPrinter.output
附錄
完整代碼
package main
import (
"flag"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"github.com/atotto/clipboard"
)
type DirectoryPrinter struct {
rootDir string
showHidden bool
currentDepth int
maxDepth int
indentSymbol string
folderSymbol string
fileSymbol string
output []string
showConnector bool // 新增字段控制是否顯示連接線
}
func (dp *DirectoryPrinter) printDirectory() error {
// 檢查是否超過最大深度
if dp.maxDepth > 0 && dp.currentDepth >= dp.maxDepth {
return nil
}
entries, err := os.ReadDir(dp.rootDir)
if err != nil {
return err
}
// 過濾隱藏文件
var filteredEntries []os.DirEntry
for _, entry := range entries {
if dp.showHidden || !strings.HasPrefix(entry.Name(), ".") {
filteredEntries = append(filteredEntries, entry)
}
}
// 按類型(目錄優(yōu)先)和名稱排序
sort.Slice(filteredEntries, func(i, j int) bool {
// 首先按類型排序(目錄在前)
if filteredEntries[i].IsDir() != filteredEntries[j].IsDir() {
return filteredEntries[i].IsDir()
}
// 同類型按名稱排序
return filteredEntries[i].Name() < filteredEntries[j].Name()
})
total := len(filteredEntries)
for i, entry := range filteredEntries {
prefix := strings.Repeat(dp.indentSymbol, dp.currentDepth)
var line string
if entry.IsDir() {
line = dp.buildFolderLine(prefix, i, total, entry)
} else {
line = dp.buildFileLine(prefix, i, total, entry)
}
dp.output = append(dp.output, line)
// 遞歸處理子文件夾
if entry.IsDir() {
childPrinter := &DirectoryPrinter{
rootDir: filepath.Join(dp.rootDir, entry.Name()),
showHidden: dp.showHidden,
currentDepth: dp.currentDepth + 1,
maxDepth: dp.maxDepth,
indentSymbol: dp.indentSymbol,
folderSymbol: dp.folderSymbol,
fileSymbol: dp.fileSymbol,
output: dp.output,
showConnector: dp.showConnector,
}
if err := childPrinter.printDirectory(); err != nil {
return err
}
dp.output = childPrinter.output
}
}
return nil
}
func (dp *DirectoryPrinter) buildFolderLine(prefix string, index, total int, entry os.DirEntry) string {
if dp.showConnector {
isLast := index == total-1
connector := "├── "
if isLast {
connector = "└── "
}
return fmt.Sprintf("%s%s?? %s\n", prefix, connector, entry.Name())
}
return fmt.Sprintf("%s?? %s\n", prefix, entry.Name())
}
func (dp *DirectoryPrinter) buildFileLine(prefix string, index, total int, entry os.DirEntry) string {
if dp.showConnector {
isLast := index == total-1
connector := "├── "
if isLast {
connector = "└── "
}
return fmt.Sprintf("%s%s?? %s\n", prefix, connector, entry.Name())
}
return fmt.Sprintf("%s?? %s\n", prefix, entry.Name())
}
func usage() {
fmt.Println("Usage: directory_printer [OPTIONS] [PATH]")
fmt.Println("\nOptions:")
fmt.Println(" --show-hidden Include hidden files and directories")
fmt.Println(" --output-file <file_path> Save the directory structure to a file")
fmt.Println(" --copy-ClipBoard Copy Directory structure to clipboard")
fmt.Println(" --max-depth <number> Maximum directory depth to display (0 for all levels, 1 for root only)")
fmt.Println(" --show-connector Show connector characters (├── and └──)")
fmt.Println(" --help Display this help message")
fmt.Println("\nExample:")
fmt.Println(" directory_printer --show-hidden --max-depth 2 --output-file output.txt /path/to/directory")
}
func isDirectory(path string) bool {
fileInfo, err := os.Stat(path)
if err != nil {
return false
}
return fileInfo.IsDir()
}
func saveToFile(filePath string, content []string) error {
file, err := os.Create(filePath)
if err != nil {
return err
}
defer file.Close()
for _, line := range content {
if _, err := file.WriteString(line); err != nil {
return err
}
}
return nil
}
func main() {
showHidden := flag.Bool("show-hidden", false, "Include hidden files and directories")
outputFile := flag.String("output-file", "", "Save the directory structure to a file")
copyClipBoard := flag.Bool("copy-ClipBoard", true, "Copy Directory structure to clipboard")
maxDepth := flag.Int("max-depth", 0, "Maximum directory depth to display (0 for all levels, 1 for root only)")
showConnector := flag.Bool("show-connector", false, "Show connector characters (├── and └──)")
help := flag.Bool("help", false, "Display this help message")
flag.Parse()
if *help {
usage()
os.Exit(0)
}
var rootDir string
if len(flag.Args()) == 0 {
rootDir = "."
} else {
rootDir = flag.Arg(0)
}
if !isDirectory(rootDir) {
fmt.Printf("Error: %s is not a valid directory\n", rootDir)
os.Exit(1)
}
printer := &DirectoryPrinter{
rootDir: rootDir,
showHidden: *showHidden,
currentDepth: 0,
maxDepth: *maxDepth,
indentSymbol: " ", // 使用4個空格作為視覺縮進
folderSymbol: "",
fileSymbol: "",
output: []string{},
showConnector: *showConnector,
}
rootName := filepath.Base(rootDir)
if rootName == "." {
// 獲取當前工作目錄的絕對路徑
absPath, err := filepath.Abs(rootDir)
if err != nil {
rootName = "current_directory"
} else {
rootName = filepath.Base(absPath)
}
}
printer.output = append(printer.output, fmt.Sprintf("?? %s\n", rootName))
// 增加根目錄的縮進
printer.currentDepth = 1
err := printer.printDirectory()
if err != nil {
fmt.Printf("Error: %v\n", err)
os.Exit(1)
}
if *outputFile != "" {
err = saveToFile(*outputFile, printer.output)
if err != nil {
fmt.Printf("Failed to save to file: %v\n", err)
os.Exit(1)
}
fmt.Printf("Directory structure saved to: %s\n", *outputFile)
}
if *copyClipBoard {
content := strings.Join(printer.output, "")
err = clipboard.WriteAll(content)
if err != nil {
fmt.Printf("Failed to copy to clipboard: %v\n", err)
} else {
fmt.Println("Directory structure copied to clipboard")
}
}
fmt.Println("\n=== Directory Structure ===")
for _, line := range printer.output {
fmt.Print(line)
}
}
總結(jié)
這款Go語言實現(xiàn)的目錄樹打印工具通過簡潔的代碼實現(xiàn)了強大的功能,無論是開發(fā)者快速查看項目結(jié)構,還是編寫技術文檔時展示目錄布局,它都是一個得力的助手。清晰的emoji標識、靈活的輸出選項和可定制的顯示深度,讓它成為你開發(fā)工具箱中不可或缺的一員。
到此這篇關于基于Go語言實現(xiàn)一個目錄樹打印工具的文章就介紹到這了,更多相關Go目錄樹打印內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
golang xorm及time.Time自定義解決json日期格式的問題
這篇文章主要介紹了golang xorm及time.Time自定義解決json日期格式的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12
gin正確多次讀取http?request?body內(nèi)容實現(xiàn)詳解
這篇文章主要為大家介紹了gin正確多次讀取http?request?body內(nèi)容實現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-01-01
golang踩坑實戰(zhàn)之channel的正確使用方式
Golang?channel是Go語言中一個非常重要的特性,除了用來處理并發(fā)編程的任務中,它還可以用來進行消息傳遞和事件通知,這篇文章主要給大家介紹了關于golang踩坑實戰(zhàn)之channel的正確使用方式,需要的朋友可以參考下2023-06-06
Golang對MongoDB數(shù)據(jù)庫的操作簡單封裝教程
mongodb官方?jīng)]有關于go的mongodb的驅(qū)動,因此只能使用第三方驅(qū)動,mgo就是使用最多的一種。下面這篇文章主要給大家介紹了關于利用Golang對MongoDB數(shù)據(jù)庫的操作簡單封裝的相關資料,需要的朋友可以參考下2018-07-07
golang?channel多協(xié)程通信常用方法底層原理全面解析
channel?是?goroutine?與?goroutine?之間通信的重要橋梁,借助?channel,我們能很輕易的寫出一個多協(xié)程通信程序,今天,我們就來看看這個?channel?的常用用法以及底層原理2023-09-09

