Skip to content

Commit

Permalink
Add first version for stdsym
Browse files Browse the repository at this point in the history
  • Loading branch information
lotusirous committed Dec 26, 2023
1 parent a6761f9 commit a0c5e38
Show file tree
Hide file tree
Showing 7 changed files with 399 additions and 2 deletions.
28 changes: 26 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,26 @@
# gostdsym
Fuzzy finding go doc symbols
# Fuzzy finding go doc symbols

`stdsym` simplifies Go documentation exploration by extracting all exported
symbols from the standard library, enabling fuzzy searching through Go
documents.

## Demo

Watch a quick demo showcasing the usage of this tool:

![Demo](./demo.gif)

## Installation

Get started quickly with `stdsym`:

```
go install github.com/lotusirous/gostdsym/stdsym@latest
```

Create a handy `gdoc` alias for instant symbol lookups:

```bash
$ stdsym > ~/.gostdsym
$ alias gdoc="cat ~/.gostdsym |fzf | xargs go doc "
```
Binary file added demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module github.com/lotusirous/gostdsym

go 1.21.5

require golang.org/x/tools v0.16.1

require golang.org/x/mod v0.14.0 // indirect
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
43 changes: 43 additions & 0 deletions stdsym/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package main

import (
"bytes"
"log"
"os"

"github.com/lotusirous/gostdsym"
)

func main() {
log.SetPrefix("stdsym: ")
log.SetFlags(0)
if err := run(); err != nil {
log.Fatal(err)
}
}

func run() error {
cwd, err := os.Getwd()
if err != nil {
return err
}

pkgs, err := gostdsym.LoadPackages("std")
if err != nil {
return err
}

w := os.Stdout
var buf bytes.Buffer
for _, v := range pkgs {
out, err := gostdsym.GetPackageSymbols(v, cwd)
if err != nil {
return err
}
for _, sym := range out {
buf.WriteString(sym + "\n")
}
}
_, err = buf.WriteTo(w)
return err
}
161 changes: 161 additions & 0 deletions symbol.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package gostdsym

import (
"fmt"
"go/build"
"go/doc"
"go/parser"
"go/token"
"io/fs"
"regexp"
"slices"
"strings"

"golang.org/x/tools/go/packages"
)

var internalPkg = regexp.MustCompile(`(^|/)internal($|/)`)

func isSkipPackage(v string) bool {
return internalPkg.MatchString(v) || strings.HasPrefix(v, "vendor/")
}

// LoadPackages returns a list of packages.
func LoadPackages(pattern string) ([]string, error) {
pkgs, err := packages.Load(nil, pattern)
if err != nil {
return nil, err
}
out := make([]string, len(pkgs))
for i := 0; i < len(pkgs); i++ {
path := pkgs[i].PkgPath
if isSkipPackage(path) {
continue
}
out[i] = pkgs[i].PkgPath
}
return out, nil
}

// GetPackageSymbols extracts all exported symbols from a package.
func GetPackageSymbols(name, srcDir string) ([]string, error) {
buildPkg, err := build.Import(name, srcDir, build.ImportComment)
if err != nil {
return nil, err
}

syms, err := buildSymbols(buildPkg)
if err != nil {
return nil, err
}

syms = slices.Compact(syms)
for i := range syms {
syms[i] = buildPkg.ImportPath + "." + syms[i]
}
syms = append(syms, buildPkg.ImportPath)
return syms, nil
}

func buildSymbols(pkg *build.Package) ([]string, error) {
fset := token.NewFileSet()
include := func(info fs.FileInfo) bool {
for _, name := range pkg.GoFiles {
if name == info.Name() {
return true
}
}
return false
}
pkgs, err := parser.ParseDir(fset, pkg.Dir, include, parser.ParseComments)
if err != nil {
return nil, err
}
astPkg, ok := pkgs[pkg.Name]
if !ok {
return nil, fmt.Errorf("not found package name: %s", pkg.Name)
}
docPkg := doc.New(astPkg, pkg.ImportPath, doc.AllDecls)

typs := types(docPkg)
vars := variables(docPkg.Vars)
consts := constants(docPkg.Consts)
fns := functions(docPkg.Funcs)
return append(append(append(typs, vars...), consts...), fns...), nil
}

func types(docPkg *doc.Package) []string {
var out []string
for _, typ := range docPkg.Types {
if !token.IsExported(typ.Name) {
continue
}
out = append(out, typ.Name)
for _, va := range typ.Vars {
for _, name := range va.Names {
if name == "_" {
continue
}
out = append(out, name)
}
}
for _, va := range typ.Consts {
for _, name := range va.Names {
if name == "_" {
continue
}
out = append(out, name)
}
}
for _, fn := range typ.Funcs {
out = append(out, fn.Name)
}
for _, method := range typ.Methods {
if !token.IsExported(method.Name) {
continue
}
out = append(out, typ.Name+"."+method.Name)
}
}
return out
}

func variables(vars []*doc.Value) []string {
var out []string
for _, va := range vars {
for _, name := range va.Names {
if name == "_" {
continue
}
if token.IsExported(name) {
out = append(out, name)
}
}
}
return out
}

func constants(consts []*doc.Value) []string {
var out []string
for _, va := range consts {
for _, name := range va.Names {
if name == "_" {
continue
}
if token.IsExported(name) {
out = append(out, name)
}
}
}
return out
}

func functions(funcs []*doc.Func) []string {
var out []string
for _, fn := range funcs {
if token.IsExported(fn.Name) {
out = append(out, fn.Name)
}
}
return out
}
Loading

0 comments on commit a0c5e38

Please sign in to comment.