go結合workflow打印目錄樹到粘貼板

前幾天寫筆記的時候,想要一個目錄樹,很無奈手上沒有任何工具,只能本身照着目錄結構一個一個敲。今天就索性本身動手,用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

  • infos:指定目錄下的全部子目錄對象和文件對象的句柄集合
  • lastDirPos:集合中最後一個子目錄對象的索引位置
  • deep:遍歷的深度,關係到打印縮進符號的多少
  • currentPath:當前路徑
  • tree:打印的目錄樹
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)
	}
}
複製代碼

這個函數就是用來遞歸遍歷目錄的,它有五個參數數組

  • infos:當前目錄下的全部子目錄對象和文件對象的句柄集合
  • lastDirPos:集合中最後一個子目錄對象的索引位置
  • deep:遍歷的深度,關係到打印空格的多少
  • currentPath:當前路徑
  • tree:打印的目錄樹

該方法的運行過程是bash

  1. 打印文件部分,若是有隱藏文件則跳過
  2. 打印目錄部分,若果有隱藏目錄則跳過隱藏目錄
  3. 讀取子目錄下的目錄和文件,若是是空目錄的話則打印目錄名,開始下一次循環
  4. 對讀取出來的數組進行排序,該排序有兩個做用
    • 把目錄與文件分紅兩部分
    • 在目錄與文件這兩個部分中,照文件名進行排序
  5. 遞歸遍歷目錄

目錄與文件分類以及排序

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數據結構

  • i:表明對infos進行從左往右遍歷的下標
  • lastDirPos:最右邊的目錄對象所處的下標 排序的過程就是:
  1. 從左往右進行遍歷,找出最左邊的第一個文件對象
  2. 把最左邊的第一個文件對象與最右邊的目錄對象進行交換
  3. 調整lastDirPos,調用adjustPos函數來保證lastDirPos始終是最右邊的目錄對象的下標
  4. 當i和lastDirPos相等的時候,表示當前指針已經移到了最右邊的目錄對象,這是i自己以及i的左邊都是目錄,i的右邊都是文件對象
  5. 取出目錄對象部分,按照名字進行排序
  6. 取出文件對象部分,按照名字進行排序
  7. 將目錄對象部分排序後的結果與文件對象部分排序後的結果合併、返回

adjustPos函數

func adjustPos(infos []os.FileInfo, lastDirPos *int) {
	for !infos[*lastDirPos].IsDir() {
		*lastDirPos--
		if *lastDirPos == -1 {
			break
		}
	}
}
複製代碼

該函數就是對infos數組進行從右往左遍歷,找到最右邊的目錄對象app

  • 若是當前位置的對象還是文件,則
*lastDirPos--
複製代碼

繼續循環,直到對象是目錄時終止函數

  • 若是lastDirPos等於-1了,則表示該數組中只有文件對象,直接終止循環

名字打印

我首先定義了兩個常量,分別表示目錄類型和文件類型工具

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())
}
複製代碼

它有四個參數對象

  • file:操做的對象
  • prefix:每一行打印的前綴
  • deep:當前遍歷的深度
  • fileType:文件類型,是目錄仍是文件

其邏輯是這樣的:

  1. 根據文件類型判斷是文件仍是目錄
  2. 生成placeHolder這個變量,好比"|────────example.gif" ,placeHolder就是"|────────",它會根據深度來決定"───"的重複次數
  3. 而後輸出prefix+placeHolder+文件名

main類

前面幾個就是打印目錄的核心函數,下面是完整的代碼

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數據結構

用過alfred的都熟悉這個界面

它有5個主要的部分,如圖中方框所示:

  • 1:觸發workflow的關鍵字
  • 2:須要處理的參數,在這篇博客裏面就是文件的路徑
  • 3:列表中的某個列表的標題
  • 4:列表中的某個列表的副標題
  • 5:列表中的某個列表的icon

下面對這五個部分設置進行說明

關鍵字和參數

  1. 打開alfred的偏好設置=>選擇workflow=>點擊左下角的+=>選擇blank workflow=>填入相關信息即建立一個新的workflow

2) 選中新建立的workflow=>在右邊空白區域中右擊(由於我選的是本來存在的,全部有設置在上面),選擇圖中所示的菜單

3) 雙擊出現的script,進行圖中配置=>save

4) 再次在右邊的空白區域右擊,生成粘貼板

5) 將script與粘貼板鏈接起來

列表展現

列表有以下的數據結構,它以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表示的是一個列表數組,列表有以下屬性

  • uid:惟一id,只要惟一就行
  • type:列表類型
  • title:列表標題,即前面的方框三
  • subtitle:列表副標題,即前面的方框四
  • arg:該列表將傳遞的值,在這個博客中就是樹形圖目錄結構字符串,它將傳遞給粘貼板
  • autocomplete:自動完成,我也不太明白這個字段意思,有知道能夠告訴我
  • icon:列表的圖標,即前面的方框五

所以想要得到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的可執行文件 而後按照以下步驟:

  1. 打開該workflow所在的目錄

2)把剛纔的生成的可執行文件tree拷貝到此目錄

3)經過tree關鍵字和參數生成目錄結構,回車複製到粘貼板

4) 而後command+v,就有結果啦

|────README.md
|────go.mod
|────go.sum
|────tree.go
|────|image
|────────example.gif
|────|response
|────────icon.go
|────────info.go
|────────response.go
|────|workflow
|────────打印目錄樹.alfredworkflow
複製代碼

對這裏就學會了go和workflow結合打印目錄樹,附上github地址,給爲給個贊吧👍


github戳這裏

相關文章
相關標籤/搜索