前几天写笔记的时候,想要一个目录树,很无奈手上没有任何工具,只能本身照着目录结构一个一个敲。今天就索性本身动手,用go结合alfred写一个打印目录树到粘贴板的workflow,这是演示图git
|────README.md
|────go.mod
|────go.sum
|────tree.go
|────|image
|────────example.gif
|────|response
|────────icon.go
|────────info.go
|────────response.go
|────|workflow
|────────打印目录树.alfredworkflow
复制代码
要打印目录树,最最基础的就是对目录进行操做,下面开始敲代码吧github
先看看这个函数的五个参数json
func traverse(infos []os.FileInfo, lastDirPos, deep int, currentPath string, tree *string) {
//打印的前缀
prefix := "|"
//当前目录下子目录以及文件的总数
length := len(infos)
//首先打印出文件的名称
for j := lastDirPos + 1; j < length; j++ {
// "."开头的文件在MacOS表示隐藏文件,这里我不想打印出来
if strings.Index(infos[j].Name(), ".") == 0 {
continue
}
//经过打印函数,把打印结果添加到tree变量里
*tree += printName(infos[j], prefix, deep, FILE)
}
//而后打印目录的名称
for i := 0; i <= lastDirPos; i++ {
//同前面的文件打印,过滤掉隐藏目录
if strings.Index(infos[i].Name(), ".") == 0 {
continue
}
//当前路径+当前目录,构成下一级遍历的路径
dirPath := currentPath + "/" + infos[i].Name()
//读取下一级路径中包含的文件和子目录,得到集合
files, _ := ioutil.ReadDir(dirPath)
//若是下一级路径中没有文件和子目录,打印次目录名开始下一次循环
if len(files) == 0 {
*tree += printName(infos[i], prefix, deep, DIR)
continue
}
//对读出来的集合进行排序操做
lastDirPosC := sortFile(&files)
//打印目录名
*tree += printName(infos[i], prefix, deep, DIR)
//代码若是走到这里,则说明目录下还有子目录或者文件,进行递归遍历
traverse(files, lastDirPosC, deep+1, dirPath, tree)
}
}
复制代码
这个函数就是用来递归遍历目录的,它有五个参数数组
该方法的运行过程是bash
func sortFile(infos *[]os.FileInfo) int {
lastDirPos := len(*infos) - 1
adjustPos(*infos, &lastDirPos)
for i := 0; i < lastDirPos; i++ {
if !(*infos)[i].IsDir() {
swap(*infos, i, lastDirPos)
adjustPos(*infos, &lastDirPos)
}
}
dirSlice := (*infos)[:lastDirPos+1]
fileSlice := (*infos)[lastDirPos+1:]
sort.Slice(dirSlice, func(i, j int) bool { return dirSlice[i].Name() < dirSlice[j].Name() })
sort.Slice(fileSlice, func(i, j int) bool { return fileSlice[i].Name() < fileSlice[j].Name() })
merge := append(dirSlice, fileSlice...)
infos = &merge
return lastDirPos
}
复制代码
在目录与文件分类逻辑部分,用到两个指针,i和lastDirPos数据结构
func adjustPos(infos []os.FileInfo, lastDirPos *int) {
for !infos[*lastDirPos].IsDir() {
*lastDirPos--
if *lastDirPos == -1 {
break
}
}
}
复制代码
该函数就是对infos数组进行从右往左遍历,找到最右边的目录对象app
*lastDirPos--
复制代码
继续循环,直到对象是目录时终止函数
我首先定义了两个常量,分别表示目录类型和文件类型工具
const (
DIR = iota
FILE
)
复制代码
下面看打印函数ui
func printName(file os.FileInfo, prefix string, deep int, fileType int) string {
var placeHolder string
switch fileType {
case DIR:
placeHolder = strings.Repeat("────", deep) + "|"
break
case FILE:
placeHolder = strings.Repeat("────", deep)
}
return fmt.Sprintln(prefix + placeHolder + file.Name())
}
复制代码
它有四个参数对象
其逻辑是这样的:
前面几个就是打印目录的核心函数,下面是完整的代码
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"print/response"
"sort"
"strings"
)
const (
DIR = iota
FILE
)
func main() {
args := os.Args
if args == nil || len(args) < 2 {
Usage()
return
}
currentPath := args[1]
files, _ := ioutil.ReadDir(currentPath)
lastDirPos := sortFile(&files)
deep := 1
tree := ""
traverse(files, lastDirPos, deep, currentPath, &tree)
fmt.Println(tree)
}
func traverse(infos []os.FileInfo, lastDirPos, deep int, currentPath string, tree *string) {
prefix := "|"
length := len(infos)
for j := lastDirPos + 1; j < length; j++ {
if strings.Index(infos[j].Name(), ".") == 0 {
continue
}
*tree += printName(infos[j], prefix, deep, FILE)
}
for i := 0; i <= lastDirPos; i++ {
if strings.Index(infos[i].Name(), ".") == 0 {
continue
}
dirPath := currentPath + "/" + infos[i].Name()
files, _ := ioutil.ReadDir(dirPath)
if len(files) == 0 {
*tree += printName(infos[i], prefix, deep, DIR)
continue
}
lastDirPosC := sortFile(&files)
*tree += printName(infos[i], prefix, deep, DIR)
traverse(files, lastDirPosC, deep+1, dirPath, tree)
}
}
func printName(file os.FileInfo, prefix string, deep int, fileType int) string {
var placeHolder string
switch fileType {
case DIR:
placeHolder = strings.Repeat("────", deep) + "|"
break
case FILE:
placeHolder = strings.Repeat("────", deep)
}
return fmt.Sprintln(prefix + placeHolder + file.Name())
}
func sortFile(infos *[]os.FileInfo) int {
lastDirPos := len(*infos) - 1
adjustPos(*infos, &lastDirPos)
for i := 0; i < lastDirPos; i++ {
if !(*infos)[i].IsDir() {
swap(*infos, i, lastDirPos)
adjustPos(*infos, &lastDirPos)
}
}
dirSlice := (*infos)[:lastDirPos+1]
fileSlice := (*infos)[lastDirPos+1:]
sort.Slice(dirSlice, func(i, j int) bool { return dirSlice[i].Name() < dirSlice[j].Name() })
sort.Slice(fileSlice, func(i, j int) bool { return fileSlice[i].Name() < fileSlice[j].Name() })
merge := append(dirSlice, fileSlice...)
infos = &merge
return lastDirPos
}
func swap(infos []os.FileInfo, i, j int) {
temp := infos[i]
infos[i] = infos[j]
infos[j] = temp
}
func adjustPos(infos []os.FileInfo, lastDirPos *int) {
for !infos[*lastDirPos].IsDir() {
*lastDirPos--
if *lastDirPos == -1 {
break
}
}
}
var Usage = func() {
fmt.Println("input param")
}
复制代码
到此,就能够直接在命令行中输出一个树形结构的目录,下面介绍如何经过alfred的workflow将结果复制到粘贴板
用过alfred的都熟悉这个界面
下面对这五个部分设置进行说明
列表有以下的数据结构,它以json表示:
{
"items":[
{
"uid":"8A9673FB-8CB1-BD04-AB30-3A8D820E5727",
"type":"text",
"title":"回车复制到粘贴板",
"subtitle":"回车复制到粘贴板",
"arg":"|────README.md |────go.mod |────go.sum |────tree.go |────|image |────────example.gif |────|response |────────icon.go |────────info.go |────────response.go |────|workflow |────────打印目录树.alfredworkflow ",
"autocomplete":"false",
"icon":{
"type":"fileicon",
"path":"~/Desktop"
}
}
]
}
复制代码
items表示的是一个列表数组,列表有以下属性
所以想要得到items的相关信息,得对前面的go脚本进行修改,下面是修改后的结果
tree.go
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"print/response"
"sort"
"strings"
)
const (
DIR = iota
FILE
)
func main() {
args := os.Args
if args == nil || len(args) < 2 {
Usage()
return
}
currentPath := args[1]
files, _ := ioutil.ReadDir(currentPath)
lastDirPos := sortFile(&files)
deep := 1
tree := ""
traverse(files, lastDirPos, deep, currentPath, &tree)
info := response.Info{}
icon := response.Icon{IType: "fileicon", Path: "~/Desktop"}
info.SetProperties(response.GetUID(), "text", "回车复制到粘贴板", "回车复制到粘贴板", tree, "false", icon)
var result response.Response
var infos []response.Info
infos = append(infos, info)
result.Items = infos
jsonStr, _ := json.Marshal(result)
fmt.Println(string(jsonStr))
}
func traverse(infos []os.FileInfo, lastDirPos, deep int, currentPath string, tree *string) {
prefix := "|"
length := len(infos)
for j := lastDirPos + 1; j < length; j++ {
if strings.Index(infos[j].Name(), ".") == 0 {
continue
}
*tree += printName(infos[j], prefix, deep, FILE)
}
for i := 0; i <= lastDirPos; i++ {
if strings.Index(infos[i].Name(), ".") == 0 {
continue
}
dirPath := currentPath + "/" + infos[i].Name()
files, _ := ioutil.ReadDir(dirPath)
if len(files) == 0 {
*tree += printName(infos[i], prefix, deep, DIR)
continue
}
lastDirPosC := sortFile(&files)
*tree += printName(infos[i], prefix, deep, DIR)
traverse(files, lastDirPosC, deep+1, dirPath, tree)
}
}
func printName(file os.FileInfo, prefix string, deep int, fileType int) string {
var placeHolder string
switch fileType {
case DIR:
placeHolder = strings.Repeat("────", deep) + "|"
break
case FILE:
placeHolder = strings.Repeat("────", deep)
}
return fmt.Sprintln(prefix + placeHolder + file.Name())
}
func sortFile(infos *[]os.FileInfo) int {
lastDirPos := len(*infos) - 1
adjustPos(*infos, &lastDirPos)
for i := 0; i < lastDirPos; i++ {
if !(*infos)[i].IsDir() {
swap(*infos, i, lastDirPos)
adjustPos(*infos, &lastDirPos)
}
}
dirSlice := (*infos)[:lastDirPos+1]
fileSlice := (*infos)[lastDirPos+1:]
sort.Slice(dirSlice, func(i, j int) bool { return dirSlice[i].Name() < dirSlice[j].Name() })
sort.Slice(fileSlice, func(i, j int) bool { return fileSlice[i].Name() < fileSlice[j].Name() })
merge := append(dirSlice, fileSlice...)
infos = &merge
return lastDirPos
}
func swap(infos []os.FileInfo, i, j int) {
temp := infos[i]
infos[i] = infos[j]
infos[j] = temp
}
func adjustPos(infos []os.FileInfo, lastDirPos *int) {
for !infos[*lastDirPos].IsDir() {
*lastDirPos--
if *lastDirPos == -1 {
break
}
}
}
var Usage = func() {
fmt.Println("input param")
}
=====================================
icon.go
package response
type Icon struct {
IType string `json:"type"`
Path string `json:"path"`
}
====================================
info.go
package response
import (
"crypto/rand"
"fmt"
)
type Info struct {
Uid string `json:"uid"`
IType string `json:"type"`
Title string `json:"title"`
Subtitle string `json:"subtitle"`
Arg string `json:"arg"`
Autocomplete string `json:"autocomplete"`
Icon Icon `json:"icon"`
}
func(i *Info) SetProperties(uid,itype,title,subtitle,arg,autocomplete string,icon Icon){
i.Uid=uid
i.IType=itype
i.Title=title
i.Subtitle=subtitle
i.Arg=arg
i.Autocomplete=autocomplete
i.Icon=icon
}
func GetUID() string {
data := make([]byte, 16)
_, err := rand.Read(data)
if err != nil {
panic(err)
}
uuid := fmt.Sprintf("%X-%X-%X-%X-%X", data[0:4], data[4:6], data[6:8], data[8:10], data[10:])
return uuid
}
====================================
response.go
package response
type Response struct {
Items []Info `json:"items"`
}
====================================
代码目录结构:
|────README.md
|────go.mod
|────go.sum
|────tree.go
|────|response
|────────icon.go
|────────info.go
|────────response.go
复制代码
经过go build 或者go install 得到名称为tree的可执行文件 而后按照以下步骤:
|────README.md
|────go.mod
|────go.sum
|────tree.go
|────|image
|────────example.gif
|────|response
|────────icon.go
|────────info.go
|────────response.go
|────|workflow
|────────打印目录树.alfredworkflow
复制代码
对这里就学会了go和workflow结合打印目录树,附上github地址,给为给个赞吧👍