å°ãåã«ãç§ã¯ç§ã®äººçãç°¡çŽ åãããŠãŒãã£ãªãã£ãäœãå§ããŸããã ããã¯gomodifytagsãšåŒã°ããŸã ã ãŠãŒãã£ãªãã£ã¯ããã£ãŒã«ãåã䜿çšããŠæ§é ã¿ã°ïŒstructã¿ã°ïŒã®ãã£ãŒã«ãã«èªåçã«å
¥åããŸãã äŸïŒ
vim-goã§gomodifytagsã䜿çšããäŸ ãã®ãŠãŒãã£ãªãã£ã䜿çšãããšãè€æ°ã®æ§é ãã£ãŒã«ããç°¡åã«ç®¡çã§ããŸãã ã¿ã°ã®è¿œå ãšåé€ããªãã·ã§ã³ã®ç®¡çïŒããšãã°ãemptyemptyïŒãå€æã«ãŒã«ïŒ snake_caseãcamelCaseãªã©ïŒã®å®çŸ©ãªã©ãã§ããŸãã ãã®ãŠãŒãã£ãªãã£ã¯ã©ã®ããã«æ©èœããŸããïŒ åœŒå¥³ã¯ã©ã®Goããã¯ã䜿çšããŠããŸããïŒ ããããå€ãã®è³ªåããããŸãã
ãã®éåžžã«é·ãèšäºã§ã¯ããã®ãããªãŠãŒãã£ãªãã£ãäœæããã³æ§ç¯ããæ¹æ³ã«ã€ããŠè©³ãã説æããŸãã ããã«ã¯ãGoã³ãŒãã ãã§ãªããå€ãã®ãã³ããã³ãããããŸãã
ã³ãŒããŒã泚ãã§ãèªã¿å§ããŸãããïŒ
ãŸãããŠãŒãã£ãªãã£ãäœããã¹ãããèããŸãããã
- ãœãŒã¹ãã¡ã€ã«ãèªãã§ç解ããGoãã¡ã€ã«ã«å€æããŸãã
- é©åãªæ§é ãèŠã€ããŸãã
- ãã£ãŒã«ãåãååŸããŸãã
- æ§é ã¿ã°ããã£ãŒã«ãåã§æŽæ°ããŸãïŒå€æã«ãŒã«ãããšãã°snake_caseã«åŸã£ãŠ ïŒã
- æåŸã«ããããã®å€æŽããã¡ã€ã«ã«å ããããå¥ã®äŸ¿å©ãªæ¹æ³ã§ããã°ã©ããŒã«æäŸããŸãã
æ§é ã¿ã°ãå®çŸ©ããããšããå§ãã次ã«ãŠãŒãã£ãªãã£ãé çªã«æ§ç¯ããŠããã®éšåãšçžäºã®çžäºäœçšã調ã¹ãŸãã
å€ã¿ã°ïŒããšãã°ããã®ã³ã³ãã³ãjson:"foo"
ïŒã¯ãå
¬åŒã®ä»æ§ã«ã¯èšèŒãããŠããŸãã ã ãã ããreflectããã±ãŒãžã«ã¯ãstdlibããã±ãŒãžã䜿çšãã圢åŒïŒ encoding / jsonãªã© ïŒã§ãã®ã¿ã°ãå®çŸ©ããéå
¬åŒã®ä»æ§ããããŸãã ããã¯ã reflect.StructTagåãä»ããŠè¡ãããŸã ã
å®çŸ©ã¯ç°¡åã§ã¯ãããŸãããç解ããŸãããã
- æ§é ã¿ã°ã¯æååãªãã©ã«ã§ãïŒæåååãåç
§ããããïŒã
- ããŒã¯åŒçšç¬Šã§å²ãŸããŠããªãæååãªãã©ã«ã§ã ã
- å€ã¯ã åŒçšç¬Šã§å²ãŸããæååãªãã©ã«ã§ã ã
- ããŒãšå€ã¯ã³ãã³ïŒ:)ã§åºåãããŸãã ãã¹ãŠããŸãšããŠããŒãšå€ã®ãã¢ãšåŒã³ãŸã ã
- æ§é ã¿ã°ã«ã¯ãè€æ°ã®ããŒãšå€ã®ãã¢ãå«ããããšãã§ããŸã ïŒå¿
èŠãªå ŽåïŒã ãã¢ã¯ã¹ããŒã¹ã§åºåãããŸã ã
- ãªãã·ã§ã³ã®èšå®ã¯ãå®çŸ©ã«ã¯èšèŒãããŠããŸããã encoding / jsonã®ãããªããã±ãŒãžã¯ãã³ã³ããåºåãæåãšããŠäœ¿çšããããªã¹ãã®å€ãèªã¿åããŸãã æåã®ã³ã³ãã®åŸã«æ¥ããã®ã¯ãã¹ãŠãªãã·ã§ã³ã®äžéšã§ãã ããšãã°ããªã¹ãã fooã nullemptyãstringãã¯ãå€ãfooããšãªãã·ã§ã³[ãomitemptyãããstringã]ããªã¹ãããŸãã
- æ§é ã¿ã°ã¯æååãªãã©ã«ã§ãããããäºéåŒçšç¬ŠãŸãã¯éã¢ãã¹ãããã£ïŒå·Šã®äžéåŒçšç¬ŠïŒã§å²ãå¿
èŠããããŸãã
ããããã¹ãŠã®ã«ãŒã«ãç¹°ãè¿ããŸãã
æ§é ã¿ã°ã®å®çŸ©ã«ã¯å€ãã®æçœã§ãªã詳现ããããŸãã
ããã§ãæ§é ã¿ã°ãäœã§ããããããããå¿
èŠã«å¿ããŠç°¡åã«å€æŽã§ããŸãã 質åã¯ãä»ããã解æããŠç°¡åã«å€æŽããæ¹æ³ã§ããïŒ å¹žãã reflect.StructTagã«ã¯ãã¿ã°ã解æããŠç¹å®ã®ããŒã®å€ãè¿ãã¡ãœããããããŸãã äŸïŒ
package main import ( "fmt" "reflect" ) func main() { tag := reflect.StructTag(`species:"gopher" color:"blue"`) fmt.Println(tag.Get("color"), tag.Get("species")) }
ç»é¢ã«è¡šç€ºããŸãïŒ
blue gopher
ããŒãååšããªãå Žåã空ã®æååãè¿ãããŸãã
ããããéåžžã«äŸ¿å©ã§ãããããæè»ãªãœãªã¥ãŒã·ã§ã³ãå¿
èŠãªããããã®æ©èœã圹ã«ç«ããªãå¶éããããŸãã å¶éã®ãªã¹ãïŒ
- ã¿ã°å
ã®ãšã©ãŒã¯èªèããŸããïŒããšãã°ãåŒçšç¬Šã§å²ãŸããããŒãåŒçšç¬Šã®ãªãå€ãªã©ïŒã
- ãªãã·ã§ã³ã®ã»ãã³ãã£ã¯ã¹ãèªèããŸããã
- æ¢åã®ã¿ã°ã調ã¹ãããè¿ãããããããšã¯ã§ããŸããã ã©ã®ã¿ã°ãå€æŽããããäºåã«ç¥ãå¿
èŠããããŸãã ãããŠãååãäžæãªå Žåã¯ïŒ
- æ¢åã®ã¿ã°ã¯å€æŽã§ããŸããã
- æ°ããæ§é ã¿ã°ãæåããäœæããããšã¯ã§ããŸããã
Goããã±ãŒãžãäœæããŸãããããã¯ãããããã¹ãŠã®æ¬ ç¹ãä¿®æ£ããæ§é ã¿ã°ã®ä»»æã®éšåãç°¡åã«å€æŽã§ããAPIãæäŸããŸãã
ããã±ãŒãžã¯structtagãšåŒã°ããããããããŠã³ããŒãã§ããŸãïŒ github.com/fatih/structtag ããã±ãŒãžã¯ãã¿ã°ãæ£ç¢ºã«è§£æããã³å€æŽã§ããŸã ã 以äžã¯å®å
šã«æ©èœããäŸã§ããã³ããŒããŠãã¹ãã§ããŸãã
package main import ( "fmt" "github.com/fatih/structtag" ) func main() { tag := `json:"foo,omitempty,string" xml:"foo"` // parse the tag tags, err := structtag.Parse(string(tag)) if err != nil { panic(err) } // iterate over all tags for _, t := range tags.Tags() { fmt.Printf("tag: %+v\n", t) } // get a single tag jsonTag, err := tags.Get("json") if err != nil { panic(err) } // change existing tag jsonTag.Name = "foo_bar" jsonTag.Options = nil tags.Set(jsonTag) // add new tag tags.Set(&structtag.Tag{ Key: "hcl", Name: "foo", Options: []string{"squash"}, }) // print the tags fmt.Println(tags) // Output: json:"foo_bar" xml:"foo" hcl:"foo,squash" }
ããã§ãæ§é ã¿ã°ã解æããå€æŽããŠæ°ããã¿ã°ãäœæã§ããŸãã 次ã«ãæ£ãããœãŒã¹Goãã¡ã€ã«ãå€æŽããå¿
èŠããããŸãã äžèšã®äŸã§ã¯ãã¿ã°ã¯ãã§ã«ããã«ãããŸãããæ¢åã®Goæ§é ããã©ã®ããã«ååŸããŸããïŒ
åçïŒ ASTãéããŠã ASTïŒ æœè±¡æ§æããªãŒ ïŒã䜿çšãããšããœãŒã¹ã³ãŒãããä»»æã®èå¥åïŒããŒãïŒãæœåºã§ããŸãã 以äžã¯ãåçŽåãããæ§é åããªãŒã§ãã
Go ast.Nodeæ§é ã¿ã€ãã®åºæ¬è¡šçŸ
ãã®ããªãŒã§ã¯ãä»»æã®èå¥åãæœåºããŠæäœã§ããŸã-æååãè§æ¬åŒ§ãªã©ããããã¯ããããASTããŒãã§è¡šãããŸã ã ããšãã°ããã£ãŒã«ãã®ååããFooããããBarãã«å€æŽããŠããããè¡šãããŒãã眮ãæããããšãã§ããŸãã æ§é ã¿ã°ã«ã€ããŠãåãã§ãã
Go ASTãååŸããã«ã¯ããœãŒã¹ãã¡ã€ã«ã解æããASTã«å€æããå¿
èŠããããŸãã ãããã¯ãã¹ãŠ1ã¹ãããã§å®è¡ãããŸãã
ãã¡ã€ã«ã解æããã«ã¯ã go / parserããã±ãŒãžã䜿çšããŠïŒãã¡ã€ã«å
šäœã®ããªãŒãæ§ç¯ããŸãïŒã go / astããã±ãŒãžã®å©ããåããŠããªãŒã調ã¹ãŸãïŒæåã§è¡ãããšãã§ããŸãããããã¯å¥ã®èšäºã®ãããã¯ã§ãïŒ å®å
šã«æ©èœããäŸã次ã«ç€ºããŸãã
package main import ( "fmt" "go/ast" "go/parser" "go/token" ) func main() { src := `package main type Example struct { Foo string` + " `json:\"foo\"` }" fset := token.NewFileSet() file, err := parser.ParseFile(fset, "demo", src, parser.ParseComments) if err != nil { panic(err) } ast.Inspect(file, func(x ast.Node) bool { s, ok := x.(*ast.StructType) if !ok { return true } for _, field := range s.Fields.List { fmt.Printf("Field: %s\n", field.Names[0].Name) fmt.Printf("Tag: %s\n", field.Tag.Value) } return false }) }
å®è¡çµæïŒ
Field: Foo Tag: `json:"foo"`
ç§ãã¡ãããããšïŒ
- åäžã®æ§é ã䜿çšããŠãæå¹ãªGoããã±ãŒãžã®äŸãå®çŸ©ããŸãã
- go / parserããã±ãŒãžã䜿çšããŠããã®æååã解æããŸãã ãŸããããã±ãŒãžã¯ãã£ã¹ã¯ãããã¡ã€ã«ïŒãŸãã¯ããã±ãŒãžå
šäœïŒãèªã¿åãããšãã§ããŸãã
- ãã¡ã€ã«ã解æããåŸãããŒãïŒå€æ°ãã¡ã€ã«ã«å²ãåœãŠãããŠããïŒã«ç§»åãã * ast.StructTypeã§å®çŸ©ãããASTããŒããæ¢ããŸãïŒASTå³ãåç
§ïŒã
ast.Inspect()
é¢æ°ã䜿çšããŠãããªãŒãäžã£ãŠãããŸãã ãã®é¢æ°ã¯ãééã£ãå€ãèŠã€ãããŸã§ãã¹ãŠã®ããŒããå埩åŠçããŸãã ããŒããèŠããå¿
èŠããªããããéåžžã«äŸ¿å©ã§ãã - æ§é ãã£ãŒã«ãã®ååãšæ§é ã¿ã°ã衚瀺ããŸãã
ããã§ã 2ã€ã®éèŠãªåé¡ã解決ã§ããŸã ã æåã«ã å
ã®Goãã¡ã€ã«ã解æã ãæ§é ã¿ã°ãååŸããæ¹æ³ãç¥ã£ãŠããŸã ïŒgo / parserã䜿çšïŒã 次ã«ã æ§é ã¿ã°ã解æããå¿
èŠã«å¿ããŠå€æŽã§ããŸãïŒ github.com/fatih/structtagã䜿çšïŒã
ãããã®2ã€ã®ã¹ãã«ã䜿çšããŠããŠãŒãã£ãªãã£ïŒgomodifytagsïŒã®æ§ç¯ãéå§ã§ããŸãã 圌女ã¯ïŒ
- ã©ã®æ§é ãå€æŽããå¿
èŠããããã瀺ãæ§æãååŸããŸãã
- ãã®æ§é ãèŠã€ããŠå€æŽããŸãã
- çµæãåºåããŸãã
gomodifytagsã¯ã»ãšãã©ã®å Žåãšãã£ã¿ãŒã«ãã£ãŠå®è¡ããããããCLIãã©ã°ãä»ããŠæ§æãæž¡ããŸãã 2çªç®ã®æ®µéã¯ããã¡ã€ã«ã®è§£æãæ£ããæ§é ã®æ€çŽ¢ãå€æŽïŒASTã䜿çšïŒãªã©ã®ããã€ãã®æé ã§æ§æãããŸãã æåŸã«ãçµæãå
ã®Goãã¡ã€ã«ã«åºåããããäœããã®ãããã³ã«ïŒJSONãªã©ïŒã§åºåããŸããããã«ã€ããŠã¯ä»¥äžã§èª¬æããŸãã
gomodifytagsã®ç°¡ç¥åãããã¡ã€ã³é¢æ°ïŒ
åã¹ãããã詳ããèŠãŠã¿ãŸãããã ãã£ãšç°¡åã«äŒããããšæããŸãã ããã§ã¯ãã¹ãŠãåãã§ãããèªã¿çµãããšããã«ããªãã§ãœãŒã¹ã³ãŒããææ¡ã§ããŸãïŒãœãŒã¹ãžã®ãªã³ã¯ã¯ããã¥ã¢ã«ã®æåŸã«ãããŸãïŒã
èšå®ãååŸããããšããå§ããŸãããã 以äžã¯ãå¿
èŠãªãã¹ãŠã®æ
å ±ãããæ§æã§ãã
type config struct { // first section - input & output file string modified io.Reader output string write bool // second section - struct selection offset int structName string line string start, end int // third section - struct modification remove []string add []string override bool transform string sort bool clear bool addOpts []string removeOpts []string clearOpt bool }
æ§æã¯3ã€ã®ã»ã¯ã·ã§ã³ã§æ§æãããŠããŸãã
æåã®ãã¡ã€ã«ã«ã¯ãã©ã®ãã¡ã€ã«ãã©ã®ããã«èªã¿èŸŒããã説æããèšå®ãå«ãŸããŠããŸãã ããã¯ãããŒã«ã«ãã¡ã€ã«ã·ã¹ãã ã®ãã¡ã€ã«åã䜿çšããããstdinããçŽæ¥å®è¡ã§ããŸãïŒéåžžã¯ãšãã£ã¿ãŒã§äœæ¥ãããšãã«äœ¿çšãããŸãïŒã æåã®ã»ã¯ã·ã§ã³ã§ã¯ãçµæãïŒå
ã®Goãã¡ã€ã«ãŸãã¯JSONã«ïŒåºåããæ¹æ³ãšãstdoutã«åºåãã代ããã«ãã¡ã€ã«ãäžæžããããã©ããã瀺ããŸãã
2çªç®ã®ã»ã¯ã·ã§ã³ã§ã¯ãæ§é ãšãã®ãã£ãŒã«ããéžæããæ¹æ³ã瀺ããŸãã ããã¯ããŸããŸãªæ¹æ³ã§å®è¡ã§ããŸãããªãã»ããïŒã«ãŒãœã«äœçœ®ïŒãæ§é ã®ååã1è¡ïŒãã£ãŒã«ããéžæããã ãïŒããŸãã¯è¡ã®ç¯å²ã䜿çšããŸãã æåŸã«ãéå§è¡ãšçµäºè¡ãæœåºããå¿
èŠããããŸãã 以äžã¯ãååã§æ§é ãéžæããéå§è¡ãšçµäºè¡ãæœåºããŠæ£ãããã£ãŒã«ããéžæããæ¹æ³ã瀺ããŠããŸãã
åæã«ããšãã£ã¿ãŒã¯ãã€ããªãã»ããã䜿çšããæ¹ãå¿«é©ã§ãã 次ã®å³ã§ã¯ãã«ãŒãœã«ã¯"Port"
ãã£ãŒã«ãã®ååã®ããåŸã«ãããããããéå§è¡ãšçµäºè¡ãç°¡åã«ååŸã§ããŸãã
æ§æã®3çªç®ã®ã»ã¯ã·ã§ã³ã¯ã structtag
ããã±ãŒãžã«ããŒã¿ãæž¡ãããšã«structtag
ãŸãã ãã¹ãŠã®ãã£ãŒã«ããèªã¿åã£ãåŸãæ§æãstructtag
ããã±ãŒãžã«æž¡ãããŸããããã«ãããæ§é ã¿ã°ã解æãããã®éšåãå€æŽã§ããŸãã ãã ããåæã«ãæ§é ãã£ãŒã«ãã¯äžæžããŸãã¯æŽæ°ãããŸããã
ãããŠãã©ã®ããã«æ§æãååŸããŸããïŒ flag
ããã±ãŒãžãååŸããæ§æå
ã®åãã£ãŒã«ãã®ãã©ã°ãäœæãããããã«ãã€ã³ãããŸãã äŸïŒ
flagFile := flag.String("file", "", "Filename to be parsed") cfg := &config{ file: *flagFile, }
æ§æå
ã®åãã£ãŒã«ãã«å¯ŸããŠåãããšãè¡ããŸã ã ãã©ã°å®çŸ©ã®å®å
šãªãªã¹ãã¯ãçŸåšã®gomodifytagãŠã£ã¶ãŒãã«ãããŸãã
èšå®ãåãåã£ãããåºæ¬çãªãã§ãã¯ãå®è¡ããŸãã
func main() { cfg := config{ ... } err := cfg.validate() if err != nil { log.Fatalln(err) } // continue parsing } // validate validates whether the config is valid or not func (c *config) validate() error { if c.file == "" { return errors.New("no file is passed") } if c.line == "" && c.offset == 0 && c.structName == "" { return errors.New("-line, -offset or -struct is not passed") } if c.line != "" && c.offset != 0 || c.line != "" && c.structName != "" || c.offset != 0 && c.structName != "" { return errors.New("-line, -offset or -struct cannot be used together. pick one") } if (c.add == nil || len(c.add) == 0) && (c.addOptions == nil || len(c.addOptions) == 0) && !c.clear && !c.clearOption && (c.removeOptions == nil || len(c.removeOptions) == 0) && (c.remove == nil || len(c.remove) == 0) { return errors.New("one of " + "[-add-tags, -add-options, -remove-tags, -remove-options, -clear-tags, -clear-options]" + " should be defined") } return nil }
ãã§ãã¯ã1ã€ã®é¢æ°ã§å®è¡ããããšããã¹ããç°¡åã«ãªããŸãã
ãã¡ã€ã«ã®è§£æã«ç§»ããŸãããïŒ
æåã«ããã¡ã€ã«ã®è§£ææ¹æ³ã«ã€ããŠãã§ã«èª¬æããŸããã ãã®å Žåã解æã¯æ§ææ§é å
ã®ã¡ãœããã«ãã£ãŠè¡ãããŸãã å®éããã¹ãŠã®ã¡ãœããã¯ãã®æ§é ã®äžéšã§ãã
func main() { cfg := config{} node, err := cfg.parse() if err != nil { return err } // continue find struct selection ... } func (c *config) parse() (ast.Node, error) { c.fset = token.NewFileSet() var contents interface{} if c.modified != nil { archive, err := buildutil.ParseOverlayArchive(c.modified) if err != nil { return nil, fmt.Errorf("failed to parse -modified archive: %v", err) } fc, ok := archive[c.file] if !ok { return nil, fmt.Errorf("couldn't find %s in archive", c.file) } contents = fc } return parser.ParseFile(c.fset, c.file, contents, parser.ParseComments) }
parse
é¢æ°ã¯parse
ãœãŒã¹ã³ãŒãã®ã¿ãparse
ast.Node
ãè¿ãããšãast.Node
ãŸãã ãã¡ã€ã«ã転éããå Žåããã¹ãŠãéåžžã«ç°¡åã§ãããã®å Žåã parser.ParseFile()
é¢æ°ã䜿çšãããŸãã *token.FileSet
ã¿ã€ããäœæããtoken.NewFileSet()
泚æããŠãã ããã c.fset
ã«ä¿åããŸããã parser.ParseFile()
é¢æ°ã«ãæž¡ããŸãã ãªãã§ïŒ
ãã¡ã€ã«ã»ãã㯠ãåãã¡ã€ã«ããšã«åããŒãã®å Žæã«é¢ããæ
å ±ãä¿åããããã«äœ¿çšãããããã§ãã ããã¯ã ast.Node
æ£ç¢ºãªäœçœ®ãååŸããããã«åŸã§éåžžã«åœ¹ç«ã¡ãŸãã ïŒast.Nodeã¯token.PosãšåŒã°ããã³ã³ãã¯ããªäœçœ®æ
å ±ã䜿çšããããšã«æ³šæããŠãã ãã token.FileSet.Position()
é¢æ°ã䜿çšã㊠token.FileSet.Position()
ã埩å·åãããš ã 詳现æ
å ±ãå«ã token.Position
ãååŸããŸããïŒ
ç¶ããŸãããã stdinãä»ããŠãœãŒã¹ãã¡ã€ã«ã転éãããšãç¶æ³ã¯ããã«èå³æ·±ããã®ã«ãªããŸãã config.modified
ãã£ãŒã«ãã¯ç°¡åãªãã¹ãã®ããã«io.Readerã§ãããå®éã«ã¯æšæºå
¥åãæž¡ããŸãã ããããstdinããäœãèªã¿ããã®ããå€æããæ¹æ³ã¯ïŒ
ãŠãŒã¶ãŒã«stdinçµç±ã§ã³ã³ãã³ããéä¿¡ãããã©ãããå°ããŸãã ãã®å ŽåããŠãŒã¶ãŒã¯--modified
ãã©ã°ïŒ ããŒã«ãã©ã°ïŒãæž¡ãå¿
èŠããããŸãã åæ Œããå Žåã¯ã c.modified
stdinãæ·»ä»ãc.modified
ã
flagModified = flag.Bool("modified", false, "read an archive of modified files from standard input") if *flagModified { cfg.modified = os.Stdin }
config.parse()
é¢æ°ãããäžåºŠèŠããšã .modified
ãã£ãŒã«ãã®æ·»ä»ãã¡ã€ã«ããã§ãã¯ããŠããããšã.modified
ãŸãã Stdinã¯ãéžæãããããã³ã«ã«åŸã£ãŠè§£æããå¿
èŠãããä»»æã®ããŒã¿ã¹ããªãŒã ã§ãã ãã®å Žåãã¢ãŒã«ã€ãã«ã¯æ¬¡ã®ãã®ãå«ãŸããŠãããšä»®å®ããŸãã
- ãã¡ã€ã«ã®ååã次ã«æ°ããè¡ã
- 10é²æ°ã®ãã¡ã€ã«ãµã€ãºã次ã«æ¹è¡ã
- ãã¡ã€ã«ã®å
容ã
ãã¡ã€ã«ãµã€ãºãããã£ãŠãããããã³ã³ãã³ããå®å
šã«è§£æã§ããŸãã äœãã倧ãããªã£ãå Žåã¯ã解æãåæ¢ããŠãã ããã
ãã®ã¢ãããŒãã¯ä»ã®ããã€ãã®ãŠãŒãã£ãªãã£ïŒ guru ã gogetdocãªã©ïŒã§äœ¿çšãããŸããç·šéè
ã¯ãã¡ã€ã«ã·ã¹ãã ã«ä¿åããã«å€æŽããããã¡ã€ã«ã®å
容ã転éã§ãããããç·šéè
ã«ãšã£ãŠéåžžã«äŸ¿å©ã§ãã ãããã£ãŠããå€æŽãã
ãããã£ãŠãããŒãããããŸããæ§é ãæ¢ããŸãããã
ã¡ã€ã³é¢æ°ã§ã¯ã findSelection()
é¢æ°ãåŒã³åºããŸããããã¯åã®ã¹ãããã§è§£æããŸãã
func main() {
cfg.findSelection()
é¢æ°ã¯ãæ§æã«åºã¥ããŠãæ§é ã®éå§äœçœ®ãšçµäºäœçœ®ãããã³æ§é ãéžæãããé åºãè¿ããŸãã æå®ãããããŒããééããéå§äœçœ®ãšçµäºäœçœ®ãè¿ããŸãïŒæ§æã®ã»ã¯ã·ã§ã³ã§èª¬æãããŠããŸãïŒã
ãã®åŸãé¢æ°ã¯* ast.StructTypeãèŠã€ãããŸã§ãã¹ãŠã®ããŒããå埩åŠçãããã®éå§äœçœ®ãšçµäºäœçœ®ããã¡ã€ã«ã«è¿ããŸãã
ããããããã¯ã©ã®ããã«è¡ãããŸããïŒ 3ã€ã®ã¢ãŒããããããšã«æ³šæããŠãã ããã è¡ ã ãªãã»ãããããã³æ§é åã«ããéžæïŒ
// findSelection returns the start and end position of the fields that are // suspect to change. It depends on the line, struct or offset selection. func (c *config) findSelection(node ast.Node) (int, int, error) { if c.line != "" { return c.lineSelection(node) } else if c.offset != 0 { return c.offsetSelection(node) } else if c.structName != "" { return c.structSelection(node) } else { return 0, 0, errors.New("-line, -offset or -struct is not passed") } }
è¡ã§éžæããã®ãæãç°¡åã§ãã ãã©ã°å€èªäœãè¿ãã ãã§ãã ãŠãŒã¶ãŒããã©ã°"--line 3,50"
æž¡ããå Žåãé¢æ°ã¯(3, 50, nil)
"--line 3,50"
(3, 50, nil)
è¿ããŸãã å€ãéžæããŠæŽæ°ã«å€æããã ãã§ãªããåæã«ãã§ãã¯ããŸãïŒ
func (c *config) lineSelection(file ast.Node) (int, int, error) { var err error splitted := strings.Split(c.line, ",") start, err := strconv.Atoi(splitted[0]) if err != nil { return 0, 0, err } end := start if len(splitted) == 2 { end, err = strconv.Atoi(splitted[1]) if err != nil { return 0, 0, err } } if start > end { return 0, 0, errors.New("wrong range. start line cannot be larger than end line") } return start, end, nil }
ãã®ã¢ãŒãã¯ãç·šéè
ãè¡ã®ã°ã«ãŒããéžæããŠéžæïŒåŒ·èª¿è¡šç€ºïŒãããšãã«äœ¿çšãããŸãã
ãªãã»ããããã³æ§é åã§éžæããã«ã¯ãããã«äœæ¥ãå¿
èŠã§ãã ãŸããå©çšå¯èœãªãã¹ãŠã®æ§é ãåéããŠããªãã»ãããèšç®ããããååã§æ€çŽ¢ãããããå¿
èŠããããŸãã ãããã£ãŠããã¹ãŠã®æ§é ãåéããŸãã
// collectStructs collects and maps structType nodes to their positions func collectStructs(node ast.Node) map[token.Pos]*structType { structs := make(map[token.Pos]*structType, 0) collectStructs := func(n ast.Node) bool { t, ok := n.(*ast.TypeSpec) if !ok { return true } if t.Type == nil { return true } structName := t.Name.Name x, ok := t.Type.(*ast.StructType) if !ok { return true } structs[x.Pos()] = &structType{ name: structName, node: x, } return true } ast.Inspect(node, collectStructs) return structs }
ast.Inspect()
é¢æ°ã¯ãASTãäžã£ãŠæ§é ãæ¢ããŸãã
æåã«ãååãæœåºã§ããããã«*ast.TypeSpec
ãå¿
èŠã§ãã *ast.StructType
ãæ€çŽ¢ãããšãæ§é èªäœã¯ããããŸãããååã¯ããããŸããã ãããã£ãŠãååãšæ§é ããŒããå«ãstructType
ã¿ã€ãã䜿çšããŸããããã¯äŸ¿å©ã§ãã åæ§é ã®äœçœ®ã¯äžæã§ããããããã®äœçœ®ããã€ã³ãã®ããŒãšããŠäœ¿çšããŸãã
ããã§ãã¹ãŠã®æ§é ãã§ããŸããããªãã»ãããšæ§é åã䜿çšããŠãã¢ãŒãã®éå§äœçœ®ãšçµäºäœçœ®ãè¿ãããšãã§ããŸãã æåã®ã±ãŒã¹ã§ã¯ãå€äœãæå®ãããæ§é å
ã«åãŸããã©ããã確èªããŸãã
func (c *config) offsetSelection(file ast.Node) (int, int, error) { structs := collectStructs(file) var encStruct *ast.StructType for _, st := range structs { structBegin := c.fset.Position(st.node.Pos()).Offset structEnd := c.fset.Position(st.node.End()).Offset if structBegin <= c.offset && c.offset <= structEnd { encStruct = st.node break } } if encStruct == nil { return 0, 0, errors.New("offset is not inside a struct") } // offset mode selects all fields start := c.fset.Position(encStruct.Pos()).Line end := c.fset.Position(encStruct.End()).Line return start, end, nil }
collectStructs()
ã䜿çšããŠãæ§é ãåéããç¹°ãè¿ããŸãã ãã¡ã€ã«ã®è§£æã«äœ¿çšããåætoken.FileSet
ãä¿åããããšãèŠããŠããŸããïŒ
ããã§ãæ§é äœã®åããŒããããªãã»ããã«é¢ããæ
å ±ãååŸããã®ã«åœ¹ç«ã¡ãŸãïŒ token.Position
埩å·åãã .Offset
ãã£ãŒã«ããååŸããŸã ïŒã æ§é ïŒãã®å Žåãååã¯encStruct
ïŒãèŠã€ãããŸã§ããã§ãã¯ããŠç¹°ãè¿ããŸãã
for _, st := range structs { structBegin := c.fset.Position(st.node.Pos()).Offset structEnd := c.fset.Position(st.node.End()).Offset if structBegin <= c.offset && c.offset <= structEnd { encStruct = st.node break } }
ãã®æ
å ±ã䜿çšããŠãèŠã€ãã£ãæ§é ã®éå§äœçœ®ãšçµäºäœçœ®ãæœåºã§ããŸãã
start := c.fset.Position(encStruct.Pos()).Line end := c.fset.Position(encStruct.End()).Line
æ§é åãéžæãããšãã«åãããžãã¯ã䜿çšããŸãã ãªãã»ãããæå®ãããæ§é å
ã«ãããã©ããããã§ãã¯ãã代ããã«ãæ£ããæ§é ãèŠã€ãããŸã§æ§é ã®ååããã§ãã¯ããŸãã
func (c *config) structSelection(file ast.Node) (int, int, error) {
éå§äœçœ®ãšçµäºäœçœ®ãååŸããããæ§é ãã£ãŒã«ãã®å€æŽã«é²ã¿ãŸãã
ã¡ã€ã³é¢æ°ã§ã¯ãåã®æé ã§è§£æãããããŒãã§cfg.rewrite()
é¢æ°ãåŒã³åºããŸãã
func main() { // ... find start and end position of the struct to be modified rewrittenNode, errs := cfg.rewrite(node, start, end) if errs != nil { if _, ok := errs.(*rewriteErrors); !ok { return errs } } // continue outputting the rewritten node }
ããã¯ãŠãŒãã£ãªãã£ã®éèŠãªéšåã§ãã æžãæãæ©èœã¯ãéå§äœçœ®ãšçµäºäœçœ®ã®éã®ãã¹ãŠã®æ§é ã®ãã£ãŒã«ããæžãæããŸãã
// rewrite rewrites the node for structs between the start and end // positions and returns the rewritten node func (c *config) rewrite(node ast.Node, start, end int) (ast.Node, error) { errs := &rewriteErrors{errs: make([]error, 0)} rewriteFunc := func(n ast.Node) bool { // rewrite the node ... } if len(errs.errs) == 0 { return node, nil } ast.Inspect(node, rewriteFunc) return node, errs }
ã芧ã®ãšãããåã³ast.Inspect()
ã䜿çšããŠãæå®ãããããŒãã®ããªãŒãäžã«ç§»åããŸãã rewriteFunc
é¢æ°å
ã§ãåãã£ãŒã«ãã®ã¿ã°ãæžãæããŸãïŒããã«ã€ããŠã¯åŸã§èª¬æããŸãïŒã
ast.Inspect()
ã«ãã£ãŠè¿ãããé¢æ°ã¯ãšã©ãŒãè¿ããªãããããšã©ãŒã¹ããŒã ïŒ errs
å€æ°ã䜿çšããŠå®çŸ©ãããïŒãäœæããããªãŒãäžã£ãŠãã£ãŒã«ããåŠçããŠãšã©ãŒã¹ããŒã ãåéããŸãã rewriteFunc
察åŠããŸãããã
rewriteFunc := func(n ast.Node) bool { x, ok := n.(*ast.StructType) if !ok { return true } for _, f := range x.Fields.List { line := c.fset.Position(f.Pos()).Line if !(start <= line && line <= end) { continue } if f.Tag == nil { f.Tag = &ast.BasicLit{} } fieldName := "" if len(f.Names) != 0 { fieldName = f.Names[0].Name }
ãã®é¢æ°ã¯AST ããŒãããšã«åŒã³åºãããããšã«æ³šæããŠãã ããã ãããã£ãŠãã¿ã€ã*ast.StructType
ããŒãã®ã¿ãæ¢ããŠããŸãã 次ã«ãæ§é äœã®ãã£ãŒã«ããå埩åŠçãå§ããŸãã
ããã§ããæ°ã«å
¥ãã®å€æ°start
ãšend
ãåã³äœ¿çšããŸãã ãã®ã³ãŒãã¯ããã£ãŒã«ããå€æŽãããã©ããã決å®ããŸãã 圌ã®äœçœ®ãstartâend
éã«ããå Žåã¯ç¶è¡ããããã§ãªãå Žåã¯æ³šæãæããŸããã
if !(start <= line && line <= end) { continue // skip processing the field }
次ã«ãã¿ã°ããããã©ããã確èªããŸãã ã¿ã°ãã£ãŒã«ããç©ºïŒ nil
ïŒã®å Žåã空ã®ã¿ã°ã§åæåããŸãã åŸã§ããã¯cfg.process()
é¢æ°ã®æ··ä¹±ãé¿ããã®ã«åœ¹ç«ã¡ãŸãïŒ
if f.Tag == nil { f.Tag = &ast.BasicLit{} }
ç¶è¡ããåã«ãé¢çœãããšã説æããŸãããã gomodifytagsã¯ãã£ãŒã«ãåãååŸããŠåŠçããããšããŸãã ãããŠããã£ãŒã«ããå¿åã®å Žåã¯ã©ããªããŸããïŒ
type Bar string type Foo struct { Bar
ãã®å Žåããã£ãŒã«ãã«ã¯ååããªã ãã¿ã€ãåã«åºã¥ããŠãã£ãŒã«ãåãæ³å®ããŸãã
// if there is a field name use it fieldName := "" if len(f.Names) != 0 { fieldName = f.Names[0].Name } // if there is no field name, get it from type's name if f.Names == nil { ident, ok := f.Type.(*ast.Ident) if !ok { continue } fieldName = ident.Name }
ãã£ãŒã«ãåãšã¿ã°å€ãåãåã£ããããã£ãŒã«ãã®åŠçãéå§ã§ããŸãã cfg.process()
é¢æ°ãåŠçãæ
åœããŸãïŒãã£ãŒã«ãåãšã¿ã°å€ãããå ŽåïŒã çµæïŒãã®å Žåãæ§é ã¿ã°ã®ãã©ãŒãããïŒãè¿ããŸããããã䜿çšããŠãæ¢åã®ã¿ã°å€ãäžæžãããŸãã
res, err := c.process(fieldName, f.Tag.Value) if err != nil { errs.Append(fmt.Errorf("%s:%d:%d:%s", c.fset.Position(f.Pos()).Filename, c.fset.Position(f.Pos()).Line, c.fset.Position(f.Pos()).Column, err)) continue } // rewrite the field with the new result,ie: json:"foo" f.Tag.Value = res
structtagãèŠããŠããå Žåãå®éã«ã¯StringïŒïŒãããã«è¿ãããŸã-ã¿ã°ã€ã³ã¹ã¿ã³ã¹ã®è¡šçŸã§ãã ã¿ã°ã®æçµè¡šçŸãè¿ãåã«ãstructtagããã±ãŒãžã®ããŸããŸãªã¡ãœããã䜿çšããŠãå¿
èŠã«å¿ããŠæ§é ãå€æŽããŸãã ç°¡ç¥åãããã¬ãã¥ãŒïŒ
structtag
, removeTags()
process()
. ( ), :
flagRemoveTags = flag.String("remove-tags", "", "Remove tags for the comma separated list of keys") if *flagRemoveTags != "" { cfg.remove = strings.Split(*flagRemoveTags, ",") }
removeTags()
, - --remove-tags
. tags.Delete()
structtag:
func (c *config) removeTags(tags *structtag.Tags) *structtag.Tags { if c.remove == nil || len(c.remove) == 0 { return tags } tags.Delete(c.remove...) return tags }
cfg.Process()
.
, : .
cfg.format()
, :
func main() {
stdout . . -, â , . -, stdout , , .
format()
:
func (c *config) format(file ast.Node, rwErrs error) (string, error) { switch c.output { case "source": // return Go source code case "json": // return a custom JSON output default: return "", fmt.Errorf("unknown output mode: %s", c.output) } }
.
(« ») ast.Node
Go-. , , .
(âJSONâ) ( ). :
type output struct { Start int `json:"start"` End int `json:"end"` Lines []string `json:"lines"` Errors []string `json:"errors,omitempty"` }
( ):
format()
. , . « » go/format AST Go-. , gofmt . « »:
var buf bytes.Buffer err := format.Node(&buf, c.fset, file) if err != nil { return "", err } if c.write { err = ioutil.WriteFile(c.file, buf.Bytes(), 0) if err != nil { return "", err } } return buf.String(), nil
format io.Writer
. ( var buf bytes.Buffer
), , -write
. , Go.
JSON . , , . . , format.Node()
, lossy .
lossy- ? :
type example struct { foo int
*ast.Field
. *ast.Field.Comment
, .
? foo bar ?
, . format.Node()
, :
type example struct { foo int bar int }
, lossy- *ast.File
. , . , , , JSON:
var buf bytes.Buffer err := format.Node(&buf, c.fset, file) if err != nil { return "", err } var lines []string scanner := bufio.NewScanner(bytes.NewBufferString(buf.String())) for scanner.Scan() { lines = append(lines, scanner.Text()) } if c.start > len(lines) { return "", errors.New("line selection is invalid") } out := &output{ Start: c.start, End: c.end, Lines: lines[c.start-1 : c.end], // cut out lines } o, err := json.MarshalIndent(out, "", " ") if err != nil { return "", err } return string(o), nil
.
以äžã§ãïŒ
, , :
gomodifytags
:
- CLI- .
go/parser
, ast.Node
.- ( ) , , .
- ,
ast.Node
( structtag). - Go, JSON .
gomodifytags , :
â ãœãŒã¹ã³ãŒã
Gophercon 2017