开发者

基于Go语言实现一个目录树打印工具

目录
  • 功能亮点
  • 技术实现解析
    • 核心数据结构
    • 关键算法逻辑
  • 使用指南
    • 命令行参数
    • 使用示例
    • 输出示例
  • 实现细节解析
    • 根目录处理
    • 输出处理逻辑
    • 递归目录遍历
  • 附录
    • 总结

      在日常开发中,我们经常需要可视化项目的目录结构。无论是编写文档、分享项目结构,还是单纯了解代码布局,一个清晰的目录树展示都至关重要。今天我将介绍一款用Go语言开发的目录树打印工具,它不仅能生成美观的目录结构图,还提供多种实用功能!

      功能亮点

      多层级展示:支持自定义深度限制

      隐藏文件处理:可选显示隐藏文件/文件夹

      多种输出方式:控制台打印、保存文件、复制到剪贴板

      美观可视化:使用emoji图标标识不同类型

      灵活连接线:可选的树形连接线展示

      智能排序:目录优先,按名称排序

      技术实现解析

      核心数据结构

      type DirectoryPrinter struct {
          rootDir       string      // 根目录路径
          showHidden    bool        // 是否显示隐藏文件
          currentDepth  int         // 当前递归深度
          maxDepth      int         // 最大深度限制
          indentSymbol  string      // 缩进符号(4个空格)
          folderSymbol  string      // 文件夹图标
          fileSymbol    string      // 文件图标
          output        []string    // 输出内容收集
          showConnector bool        // 是否显示连接线
      }
      

      关键算法逻辑

      1.文件排序策略:

      目录优先于文件

      相同类型按名称升序排列

      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 = "└── "
          }
      javascript    return fmt.Sprintf("%s%s %s\n", prefix, connector, entry.Name())
      }
      

      3.递归深度控制:

      if dp.maxDepth > 0 && dp.currentDepth >= dp.maxDepth {
          return nil
      }
      

      使用指南

      命令行参数

      参数说明示例
      --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

      使用示例

      基本用法(显示当前目录结构):

      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

      实现细节解析

      根目录处理

      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)
      		}
      	}
      
      	// 按类型(目录优先)和名称排序
      	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,android 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, entrphpy 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 sGgyzFqJonly)")
      	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 == "." {js
      		// 获取当前工作目录的绝对路径
      		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)
      	}
      }
      

      总结

      这款Go语言实现的目录树打印工具通过简洁的代码实现了强大的功能,无论是开发者快速查看项目结构,还是编写技术文档时展示目录布局,它都是一个得力的助手。清晰的emoji标识、灵活的输出选项和可定制的显示深度,让它成为你开发工具箱中不可或缺的一员。

      到此这篇关于基于Go语言实现一个目录树打印工具的文章就介绍到这了,更多相关Go目录树打印内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

      0

      上一篇:

      下一篇:

      精彩评论

      暂无评论...
      验证码 换一张
      取 消

      最新开发

      开发排行榜