0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Go】簡易 fuzzylauncher

Last updated at Posted at 2020-06-25

以前の記事: 【PowerShell】 peco を使った簡易ランチャ

最初はうまく動作してはしゃいでていたものの、なんとなくモッサリした動作が気になってきた今日この頃。勉強も兼ねて go で書き直してみました。

最終的にこのような感じになります。
202006262125035.png

制作過程

まずは 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
	}

コード全体

fuzzylauncher.go
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 でした。コンパイルが通りさえすれば大抵は問題なく動作するというのは安心感がありますね。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?