Go向けの完党なナヌティリティ䜜成ガむド


少し前に、私は私の人生を簡玠化するナヌティリティを䜜り始めたした。 これはgomodifytagsず呌ばれたす 。 ナヌティリティは、フィヌルド名を䜿甚しお構造タグstructタグのフィヌルドに自動的に入力したす。 䟋


vim-goでgomodifytagsを䜿甚する䟋


このナヌティリティを䜿甚するず、耇数の構造フィヌルドを簡単に管理できたす。 タグの远加ず削陀、オプションの管理たずえば、emptyempty、倉換ルヌル snake_case、camelCaseなどの定矩などができたす。 このナヌティリティはどのように機胜したすか 圌女はどのGoパックを䜿甚しおいたすか おそらく倚くの質問がありたす。


この非垞に長い蚘事では、このようなナヌティリティを䜜成および構築する方法に぀いお詳しく説明したす。 ここには、Goコヌドだけでなく、倚くのヒントやコツがありたす。


コヌヒヌを泚いで、読み始めたしょう


たず、ナヌティリティが䜕をすべきかを考えたしょう。


  1. ゜ヌスファむルを読んで理解し、Goファむルに倉換したす。
  2. 適切な構造を芋぀けたす。
  3. フィヌルド名を取埗したす。
  4. 構造タグをフィヌルド名で曎新したす倉換ルヌル、たずえばsnake_caseに埓っお 。
  5. 最埌に、これらの倉曎をファむルに加えるか、別の䟿利な方法でプログラマヌに提䟛したす。

構造タグを定矩するこずから始め、次にナヌティリティを順番に構築しお、その郚分ず盞互の盞互䜜甚を調べたす。



倀タグたずえば、そのコンテンツjson:"foo" は、公匏の仕様には蚘茉されおいたせん 。 ただし、reflectパッケヌゞには、stdlibパッケヌゞも䜿甚する圢匏 encoding / jsonなど でこのタグを定矩する非公匏の仕様がありたす。 これは、 reflect.StructTag型を介しお行われたす 。



定矩は簡単ではありたせん。理解したしょう。



これらすべおのルヌルを繰り返したす。



構造タグの定矩には倚くの明癜でない詳现がありたす。


これで、構造タグが䜕であるかがわかり、必芁に応じお簡単に倉曎できたす。 質問は、今それを解析しお簡単に倉曎する方法ですか 幞い、 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"` 

私たちがするこず



これで、 2぀の重芁な問題を解決できたす 。 最初に、 元のGoファむルを解析し 、構造タグを取埗する方法を知っおいたす go / parserを䜿甚。 次に、 構造タグを解析し、必芁に応じお倉曎できたす github.com/fatih/structtagを䜿甚。


これらの2぀のスキルを䜿甚しお、ナヌティリティgomodifytagsの構築を開始できたす。 圌女は


  1. どの構造を倉曎する必芁があるかを瀺す構成を取埗したす。
  2. この構造を芋぀けお倉曎したす。
  3. 結果を出力したす。

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は、遞択したプロトコルに埓っお解析する必芁がある任意のデヌタストリヌムです。 この堎合、アヌカむブには次のものが含たれおいるず仮定したす。



ファむルサむズがわかっおいるため、コンテンツを安党に解析できたす。 䜕かが倧きくなった堎合は、解析を停止しおください。


このアプロヌチは他のいく぀かのナヌティリティ guru 、 gogetdocなどで䜿甚されたす。線集者はファむルシステムに保存せずに倉曎されたファむルの内容を転送できるため、線集者にずっお非垞に䟿利です。 したがっお、「倉曎」。


したがっお、ノヌドがありたす。構造を探したしょう。



メむン関数では、 findSelection()関数を呌び出したす。これは前のステップで解析したす。


 func main() { // ... parse file and get ast.Node start, end, err := cfg.findSelection(node) if err != nil { return err } // continue rewriting the node with the start&end position } 

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) { // ... for _, st := range structs { if st.name == c.structName { encStruct = st.node } } // ... } 

開始䜍眮ず終了䜍眮を取埗したら、構造フィヌルドの倉曎に進みたす。



メむン関数では、前の手順で解析されたノヌドで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 } // anonymous field if f.Names == nil { ident, ok := f.Type.(*ast.Ident) if !ok { continue } fieldName = ident.Name } 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 } f.Tag.Value = res } return true } 

この関数は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 //this is an anonymous field } 

この堎合、フィヌルドには名前がなく 、タむプ名に基づいおフィヌルド名を想定したす。


 // 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() { // ... rewrite the node out, err := cfg.format(rewrittenNode, errs) if err != nil { return err } fmt.Println(out) } 

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 // this is a lossy comment bar 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


:



gomodifytags , :



→ ゜ヌスコヌド


Gophercon 2017



Source: https://habr.com/ru/post/J341822/


All Articles