以前の記事: 【PowerShell】 peco を使った簡易ランチャ
最初はうまく動作してはしゃいでていたものの、なんとなくモッサリした動作が気になってきた今日この頃。勉強も兼ねて go で書き直してみました。
制作過程
まずは Test-Path
に相当するファイルの有無を確認する関数。
func isVaridPath(filename string) bool {
_, err := os.Stat(filename)
return err == nil
}
次は Get-Content
の代わりのテキストファイル読み取り関数。
先に make
しておくとベターとの記事を見かけたので組み込んでますが容量はどの値を指定すればいいのでしょうか……(勉強不足)。とりあえずキリの良い100で。
func readFile(filePath string) []string {
f, err := os.Open(filePath)
if err != nil {
panic(err)
}
defer f.Close()
lines := make([]string, 0, 100)
scanner := bufio.NewScanner(f)
for scanner.Scan() {
lines = append(lines, scanner.Text())
}
if err := scanner.Err(); err != nil {
panic(err)
}
return lines
}
苦労したのは Get-Childitem -depth
を go で実装する処理。
最初は filepath.Walk
を使っていたのですが、 godirwalk というライブラリが探索の速さを謳っていたので我流で組み込んでいます。
こちらも容量の値はよく理解しないまま1000を指定。理由はテキストファイルから読み込むよりも確実にスライスが長くなるからです。そしてポインタにはまだ理解が及んでいないので写経状態。
func dirWalkDepth(dirname string, depth int) []string {
dirs := make([]string, 0, 1000)
err := godirwalk.Walk(dirname, &godirwalk.Options{
FollowSymbolicLinks: false,
Callback: func(osPathname string, de *godirwalk.Dirent) error {
rel, _ := filepath.Rel(dirname, osPathname)
if strings.Count(rel, `\`) >= depth {
return filepath.SkipDir
}
if de.IsDir() {
if strings.HasPrefix(de.Name(), ".") {
return filepath.SkipDir
}
dirs = append(dirs, osPathname)
}
return nil
},
ErrorCallback: func(osPathname string, err error) godirwalk.ErrorAction {
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
return godirwalk.SkipNode
},
})
if err != nil {
panic(err)
}
return dirs
}
最後はメインのフィルタリング処理。ライブラリとしての fuzzy-finder が提供されていました。大変ありがたいことです。
実装はドキュメントのサンプルをほぼコピペ。
fuzzyfinder.Find
で呼んだ関数の戻り値が実際に表示されるようなので、スライスの要素に |
が含まれていればその後ろ、そうでなければ filepath.Base
でパス末端を取得します。 Escape や Ctrl-C でキャンセルされた場合は err
が戻ります。
idx, err := fuzzyfinder.Find(rootSlice, func(i int) string {
sliceItem := rootSlice[i]
if strings.Contains(sliceItem, "|") {
return strings.Split(sliceItem, "|")[1]
}
return filepath.Base(sliceItem)
})
if err != nil {
return
}
コード全体
package main
import (
"bufio"
"fmt"
"os"
"os/exec"
"path/filepath"
"sort"
"strings"
"github.com/karrick/godirwalk"
"github.com/ktr0731/go-fuzzyfinder"
)
const (
dataPath = `C:\Personal\launch.txt`
)
func main() {
if !isVaridPath(dataPath) {
fmt.Println("cannot find data file...")
return
}
rootSlice := []string{}
for _, p := range readFile(dataPath) {
if isVaridPath(strings.Split(p, "|")[0]) {
rootSlice = append(rootSlice, p)
}
}
sort.Slice(rootSlice, func(i, j int) bool {
return filepath.Base(rootSlice[i]) < filepath.Base(rootSlice[j])
})
idx, err := fuzzyfinder.Find(rootSlice, func(i int) string {
sliceItem := rootSlice[i]
if strings.Contains(sliceItem, "|") {
return strings.Split(sliceItem, "|")[1]
}
return filepath.Base(sliceItem)
})
if err != nil {
return
}
src1 := strings.Split(rootSlice[idx], "|")[0]
if f, _ := os.Stat(src1); !f.IsDir() {
exec.Command("cmd", "/c", "start", "", src1).Start()
return
}
subDir := dirWalkDepth(src1, 4)
src2 := subDir[0]
if len(subDir) > 1 {
idx, err := fuzzyfinder.Find(subDir, func(i int) string {
relPath, _ := filepath.Rel(src1, subDir[i])
return relPath
})
if err != nil {
return
}
src2 = subDir[idx]
}
exec.Command("explorer.exe", src2).Start()
}
func isVaridPath(filename string) bool {
_, err := os.Stat(filename)
return err == nil
}
func readFile(filePath string) []string {
f, err := os.Open(filePath)
if err != nil {
panic(err)
}
defer f.Close()
lines := make([]string, 0, 100)
scanner := bufio.NewScanner(f)
for scanner.Scan() {
lines = append(lines, scanner.Text())
}
if err := scanner.Err(); err != nil {
panic(err)
}
return lines
}
func dirWalkDepth(dirname string, depth int) []string {
dirs := make([]string, 0, 1000)
err := godirwalk.Walk(dirname, &godirwalk.Options{
FollowSymbolicLinks: false,
Callback: func(osPathname string, de *godirwalk.Dirent) error {
rel, _ := filepath.Rel(dirname, osPathname)
if strings.Count(rel, `\`) >= depth {
return filepath.SkipDir
}
if de.IsDir() {
if strings.HasPrefix(de.Name(), ".") {
return filepath.SkipDir
}
dirs = append(dirs, osPathname)
}
return nil
},
ErrorCallback: func(osPathname string, err error) godirwalk.ErrorAction {
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
return godirwalk.SkipNode
},
})
if err != nil {
panic(err)
}
return dirs
}
感想
コピー&ペーストでできたフランケンシュタインの怪物のようなコードですが動いているので満足です。
はじめての go でした。コンパイルが通りさえすれば大抵は問題なく動作するというのは安心感がありますね。