decl

package module
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 12, 2025 License: MIT Imports: 7 Imported by: 0

README

decl

A package to load comments in source code. Can be used as directive parser for generate tools.

package main

import (
	"flag"
	"fmt"
	"go/format"
	"os"
	"strings"
	"time"

	"github.com/hauntedness/decl"
	"github.com/hauntedness/std/hv"
	"github.com/valyala/fasttemplate"
)

//gen:str
type Book struct {
	name *string
	//gen:str --format="2016"
	year  *time.Time
	words uint
	sale  hv.Option[bool]
	//gen:str --ignore
	dirty map[any]any
}

//go:generate go run github.com/hauntedness/ebg/cmd/stringer

//nolint:funlen //+nolint
func main() {
	//
	pkg := flag.String("pkg", ".", "package to scan")
	writeTo := flag.String("w", "generated_struct_stringer.go", "package to scan")
	flag.Parse()
	//
	thepkg, err := decl.Load(*pkg)
	if err != nil {
		panic(err)
	}
	wb := &strings.Builder{}
	prefix := "//gen:str"
	for info, comment := range thepkg.Structs() {
		if _, ok := comment.LookupValue(prefix); !ok {
			continue
		}
		def := []string{}
		val := []string{}
		for f := range info.Underlying.Fields() {
			cmd, _ := thepkg.CommentsAt(f.Pos()).Collect(decl.CutPrefix(prefix))
			if _, ignore := cmd["--ignore"]; !ignore {
				fname := f.Name()
				def = append(def, fname+" string")
				typ := f.Type().String()
				switch typ {
				case "time.Time":
					format := cmd["--format"]
					tmpl := `
						v.{{fname}} = s.{{fname}}.Format({{fformat}})
					`
					val = append(val, Render(tmpl, map[string]any{"fname": fname, "fformat": format}))
				case "int", "int8", "int16", "int32", "int64":
					tmpl := `
						v.{{fname}} = strconv.FormatInt(int64(s.{{fname}}), 10)
					`
					val = append(val, Render(tmpl, map[string]any{"fname": fname}))
				case "uint", "uint8", "uint16", "uint32", "uint64":
					tmpl := `
						v.{{fname}} = strconv.FormatUint(uint64(s.{{fname}}), 10)
					`
					val = append(val, Render(tmpl, map[string]any{"fname": fname}))
				case "*string":
					tmpl := `
						if s.{{fname}} != nil {
							v.{{fname}} = *s.{{fname}}
						}
					`
					val = append(val, Render(tmpl, map[string]any{"fname": fname}))
				case "*time.Time":
					format := cmd["--format"]
					tmpl := `
						if s.{{fname}} != nil {
							v.{{fname}} = s.{{fname}}.Format({{fformat}})
						}
					`
					val = append(val, Render(tmpl, map[string]any{"fname": fname, "fformat": format}))
				default:
					if strings.HasPrefix(typ, "*") {
						tmpl := `
							if s.{{fname}} != nil {
								v.{{fname}} = fmt.Sprint(*s.{{fname}})
							}
						`
						val = append(val, Render(tmpl, map[string]any{"fname": fname}))
					} else if strings.HasPrefix(typ, "github.com/hauntedness/std/hv.Option") {
						tmpl := `
							if s.{{fname}}.IsPresent() {
								v.{{fname}} = fmt.Sprint(s.{{fname}}.MustGet())
							}
						`
						val = append(val, Render(tmpl, map[string]any{"fname": fname}))
					} else {
						tmpl := `
								v.{{fname}} = fmt.Sprint(s.{{fname}})
						`
						val = append(val, Render(tmpl, map[string]any{"fname": fname}))
					}
				}
			}
		}
		result := Render(body, map[string]any{
			"typeName": info.TypeName.Name(),
			"fieldDef": strings.Join(def, "; "),
			"fieldVal": strings.Join(val, "\n"),
		})
		data, err := format.Source([]byte(result))
		if err != nil {
			fmt.Println(result)
			panic(err)
		}
		wb.Write(data)
		wb.WriteString("\n")
	}
	if wb.Len() > 0 {
		file, err := os.Create(*writeTo)
		if err != nil {
			panic(err)
		}
		defer file.Close()
		_, err = file.WriteString(Render(header, map[string]any{"package": thepkg.Package.Name}))
		if err != nil {
			panic(err)
		}
		_, err = file.WriteString(wb.String())
		if err != nil {
			panic(err)
		}
	}
}

func Render(template string, m map[string]any) string {
	return fasttemplate.ExecuteString(template, "{{", "}}", m)
}

var header = `// Code generated by cmd/stringer. DO NOT EDIT.
package {{package}}

import (
	"fmt"	
	"strconv"
	"time"

	"github.com/hauntedness/std/hv"
)

// force import 
var _ hv.Option[struct{}]
var _ time.Time
var _ = strconv.Atoi
`

var body = `
func (s *{{typeName}}) String() string {
	var v = struct {
		{{fieldDef}}
	} {}
	{{fieldVal}}
	return fmt.Sprintf("%+v", v)
}
`

Documentation

Index

Constants

This section is empty.

Variables

Functions

func CutPrefix added in v0.2.0

func CutPrefix(prefix string) func(string) (string, bool)

func HasPrefix added in v0.3.0

func HasPrefix(prefix string) func(string) bool

func LoadMap added in v0.5.0

func LoadMap(cfg *packages.Config, patterns ...string) (map[string]*Package, error)

func TypeName added in v0.4.0

func TypeName(typ types.Type) string

func TypePkg added in v0.4.0

func TypePkg(typ types.Type) *types.Package

Types

type Comments

type Comments []string

func (Comments) At

func (c Comments) At(index int) string

At return comment at index, if index = -1, return the last one.

func (Comments) Collect added in v0.2.0

func (c Comments) Collect(fn func(line string) (string, bool)) (map[string]string, bool)

Collect call fn to convert comment into literal 'key1=value1 key2=value2', then collect the key and value pairs into map. Collect return true if any line converted by fn.

func (Comments) Filter

func (c Comments) Filter(f func(line string) bool) Comments

func (Comments) Lookup added in v0.1.0

func (c Comments) Lookup(fn func(line string) (string, bool)) (string, bool)

Lookup execute fn on comment and return the 1st matched result.

func (Comments) LookupValue added in v0.1.0

func (c Comments) LookupValue(prefix string) (string, bool)

LookupValue remove the prefix from 1st matched comment and return the remaining.

func (Comments) String

func (c Comments) String() string

type FuncInfo

type FuncInfo struct {
	Definition types.Object
	Func       *types.Func
}

type InterfaceInfo

type InterfaceInfo struct {
	Named      *types.Named
	Underlying *types.Interface
}

type Package

type Package struct {
	// package provider type information we need.
	*packages.Package
	// contains filtered or unexported fields
}

Package with doc associated.

func Load

func Load(pkg string) (*Package, error)

func LoadPackage

func LoadPackage(pkg *packages.Package) (*Package, error)

LoadPackage make sure pkg was loaded with correct LoadMode.

func (*Package) Comments

func (pkg *Package) Comments(obj types.Object) Comments

Comments return comments in text slices, remove then '\n' in the end and nil values.

func (*Package) CommentsAt

func (pkg *Package) CommentsAt(pos token.Pos) Comments

CommentsAt return comments at pos, remove line feed and nil comment group.

func (*Package) CommentsRaw

func (pkg *Package) CommentsRaw(pos token.Pos) []*ast.CommentGroup

CommentsRaw return raw comment group, including nil.

func (*Package) DefinedTypes

func (pkg *Package) DefinedTypes() iter.Seq2[*types.Named, Comments]

func (*Package) Definitions

func (pkg *Package) Definitions() iter.Seq2[types.Object, Comments]

Definitions return iterator over each object and its comments.

func (*Package) Funcs

func (pkg *Package) Funcs() iter.Seq2[FuncInfo, Comments]

Funcs return iterator over each funcs (including abstract ones) and its comments.

func (*Package) Interfaces

func (pkg *Package) Interfaces() iter.Seq2[InterfaceInfo, Comments]

Interfaces return iterator over each struct and its comments.

func (*Package) NamedTypes added in v0.4.1

func (pkg *Package) NamedTypes() iter.Seq2[*types.Named, Comments]

NamedTypes return iterator over each defined type and its comments.

func (*Package) Structs

func (pkg *Package) Structs() iter.Seq2[StructInfo, Comments]

Structs return iterator over each struct and its comments.

type StructInfo

type StructInfo struct {
	Named      *types.Named
	Underlying *types.Struct
}

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL