// Copyright 2020 ChaiShushan . All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package main import ( "fmt" "go/ast" "go/importer" "go/parser" "go/token" "go/types" "log" "path/filepath" "strings" "time" "github.com/chai2010/gettext-go/po" ) type Package struct { path string pkgpath string pkgname string filesAbspath []string fset *token.FileSet astFiles []*ast.File typesInfo *types.Info typesPackage *types.Package potFile *po.File } func LoadPackage(path string) *Package { p := &Package{ path: path, pkgpath: gopkgPath(path), pkgname: gopkgName(path), filesAbspath: gopkgFilesAbspath(path), } var fset = token.NewFileSet() var astFiles = make([]*ast.File, len(p.filesAbspath)) for i, path := range p.filesAbspath { f, err := parser.ParseFile(fset, path, nil, 0) if err != nil { log.Fatal(err) } astFiles[i] = f } // https://github.com/golang/go/issues/26504 typesConfig := &types.Config{ Importer: importer.For("source", nil), FakeImportC: true, } typesInfo := &types.Info{ Types: make(map[ast.Expr]types.TypeAndValue), Defs: make(map[*ast.Ident]types.Object), Uses: make(map[*ast.Ident]types.Object), Selections: make(map[*ast.SelectorExpr]*types.Selection), } typesPackage, err := typesConfig.Check(p.pkgname, fset, astFiles, typesInfo) if err != nil { log.Fatal(err) } p.fset = fset p.astFiles = astFiles p.typesInfo = typesInfo p.typesPackage = typesPackage return p } func (p *Package) GenPotFile() *po.File { p.potFile = &po.File{ MimeHeader: po.Header{ Comment: po.Comment{ TranslatorComment: "" + fmt.Sprintf("package: %s\n\n", p.pkgpath) + "Generated By gettext-go" + "\n" + "https://github.com/chai2010/gettext-go" + "\n", }, ProjectIdVersion: "1.0", POTCreationDate: time.Now().Format("2006-01-02 15:04-0700"), LanguageTeam: "golang-china", Language: "zh_CN", MimeVersion: "MIME-Version: 1.0", ContentType: "Content-Type: text/plain; charset=UTF-8", ContentTransferEncoding: "Content-Transfer-Encoding: 8bit", }, } for _, f := range p.astFiles { ast.Inspect(f, func(n ast.Node) bool { switch x := n.(type) { case *ast.CallExpr: switch sel := x.Fun.(type) { case *ast.SelectorExpr: if p.isGettextPackage(sel.X) { p.processGettext(x, sel.Sel.Name) } } } return true }) } return p.potFile } func (p *Package) isGettextPackage(node ast.Node) bool { inner := p.typesPackage.Scope().Innermost(node.Pos()) if ident, ok := node.(*ast.Ident); ok { if _, obj := inner.LookupParent(ident.Name, node.Pos()); obj != nil { if pkgName, ok := obj.(*types.PkgName); ok { if pkg := pkgName.Imported(); pkg != nil { if pkg.Path() == "github.com/chai2010/gettext-go" { return true } } } } } return false } func (p *Package) processGettext(x *ast.CallExpr, fnName string) { switch fnName { case "Gettext": // Gettext(msgid string) string pos := p.fset.Position(x.Pos()) p.potFile.Messages = append(p.potFile.Messages, po.Message{ Comment: po.Comment{ ReferenceFile: []string{p.pkgpath + "/" + filepath.Base(pos.Filename)}, ReferenceLine: []int{pos.Line}, Flags: []string{"go-format"}, }, MsgContext: "", MsgId: p.evalStringValue(x.Args[0]), }) case "PGettext": // PGettext(msgctxt, msgid string) string pos := p.fset.Position(x.Pos()) p.potFile.Messages = append(p.potFile.Messages, po.Message{ Comment: po.Comment{ ReferenceFile: []string{p.pkgpath + "/" + filepath.Base(pos.Filename)}, ReferenceLine: []int{pos.Line}, Flags: []string{"go-format"}, }, MsgContext: p.evalStringValue(x.Args[0]), MsgId: p.evalStringValue(x.Args[1]), }) case "NGettext": // NGettext(msgid, msgidPlural string, n int) string // TODO case "PNGettext": // PNGettext(msgctxt, msgid, msgidPlural string, n int) string // TODO case "DGettext": // DGettext(domain, msgid string) string // TODO case "DPGettext": // DPGettext(domain, msgctxt, msgid string) string // TODO case "DNGettext": // DNGettext(domain, msgid, msgidPlural string, n int) string // TODO case "DPNGettext": // DPNGettext(domain, msgctxt, msgid, msgidPlural string, n int) string // TODO } } func (p *Package) evalStringValue(val interface{}) string { switch val.(type) { case *ast.BasicLit: s := val.(*ast.BasicLit).Value s = strings.TrimSpace(s) s = strings.Trim(s, `"`+"\n") return s case *ast.BinaryExpr: if val.(*ast.BinaryExpr).Op != token.ADD { return "" } left := p.evalStringValue(val.(*ast.BinaryExpr).X) right := p.evalStringValue(val.(*ast.BinaryExpr).Y) return left[0:len(left)-1] + right[1:len(right)] default: panic(fmt.Sprintf("unknown type: %v", val)) } }