...

Source file src/github.com/chai2010/gettext-go/cmd/xgettext-go/pkg.go

Documentation: github.com/chai2010/gettext-go/cmd/xgettext-go

     1  // Copyright 2020 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"fmt"
     9  	"go/ast"
    10  	"go/importer"
    11  	"go/parser"
    12  	"go/token"
    13  	"go/types"
    14  	"log"
    15  	"path/filepath"
    16  	"strings"
    17  	"time"
    18  
    19  	"github.com/chai2010/gettext-go/po"
    20  )
    21  
    22  type Package struct {
    23  	path         string
    24  	pkgpath      string
    25  	pkgname      string
    26  	filesAbspath []string
    27  
    28  	fset         *token.FileSet
    29  	astFiles     []*ast.File
    30  	typesInfo    *types.Info
    31  	typesPackage *types.Package
    32  
    33  	potFile *po.File
    34  }
    35  
    36  func LoadPackage(path string) *Package {
    37  	p := &Package{
    38  		path:         path,
    39  		pkgpath:      gopkgPath(path),
    40  		pkgname:      gopkgName(path),
    41  		filesAbspath: gopkgFilesAbspath(path),
    42  	}
    43  
    44  	var fset = token.NewFileSet()
    45  	var astFiles = make([]*ast.File, len(p.filesAbspath))
    46  
    47  	for i, path := range p.filesAbspath {
    48  		f, err := parser.ParseFile(fset, path, nil, 0)
    49  		if err != nil {
    50  			log.Fatal(err)
    51  		}
    52  		astFiles[i] = f
    53  	}
    54  
    55  	// https://github.com/golang/go/issues/26504
    56  	typesConfig := &types.Config{
    57  		Importer:    importer.For("source", nil),
    58  		FakeImportC: true,
    59  	}
    60  	typesInfo := &types.Info{
    61  		Types:      make(map[ast.Expr]types.TypeAndValue),
    62  		Defs:       make(map[*ast.Ident]types.Object),
    63  		Uses:       make(map[*ast.Ident]types.Object),
    64  		Selections: make(map[*ast.SelectorExpr]*types.Selection),
    65  	}
    66  
    67  	typesPackage, err := typesConfig.Check(p.pkgname, fset, astFiles, typesInfo)
    68  	if err != nil {
    69  		log.Fatal(err)
    70  	}
    71  
    72  	p.fset = fset
    73  	p.astFiles = astFiles
    74  	p.typesInfo = typesInfo
    75  	p.typesPackage = typesPackage
    76  
    77  	return p
    78  }
    79  
    80  func (p *Package) GenPotFile() *po.File {
    81  	p.potFile = &po.File{
    82  		MimeHeader: po.Header{
    83  			Comment: po.Comment{
    84  				TranslatorComment: "" +
    85  					fmt.Sprintf("package: %s\n\n", p.pkgpath) +
    86  					"Generated By gettext-go" + "\n" +
    87  					"https://github.com/chai2010/gettext-go" + "\n",
    88  			},
    89  			ProjectIdVersion:        "1.0",
    90  			POTCreationDate:         time.Now().Format("2006-01-02 15:04-0700"),
    91  			LanguageTeam:            "golang-china",
    92  			Language:                "zh_CN",
    93  			MimeVersion:             "MIME-Version: 1.0",
    94  			ContentType:             "Content-Type: text/plain; charset=UTF-8",
    95  			ContentTransferEncoding: "Content-Transfer-Encoding: 8bit",
    96  		},
    97  	}
    98  
    99  	for _, f := range p.astFiles {
   100  		ast.Inspect(f, func(n ast.Node) bool {
   101  			switch x := n.(type) {
   102  			case *ast.CallExpr:
   103  				switch sel := x.Fun.(type) {
   104  				case *ast.SelectorExpr:
   105  					if p.isGettextPackage(sel.X) {
   106  						p.processGettext(x, sel.Sel.Name)
   107  					}
   108  				}
   109  			}
   110  			return true
   111  		})
   112  	}
   113  
   114  	return p.potFile
   115  }
   116  
   117  func (p *Package) isGettextPackage(node ast.Node) bool {
   118  	inner := p.typesPackage.Scope().Innermost(node.Pos())
   119  	if ident, ok := node.(*ast.Ident); ok {
   120  		if _, obj := inner.LookupParent(ident.Name, node.Pos()); obj != nil {
   121  			if pkgName, ok := obj.(*types.PkgName); ok {
   122  				if pkg := pkgName.Imported(); pkg != nil {
   123  					if pkg.Path() == "github.com/chai2010/gettext-go" {
   124  						return true
   125  					}
   126  				}
   127  			}
   128  		}
   129  	}
   130  	return false
   131  }
   132  
   133  func (p *Package) processGettext(x *ast.CallExpr, fnName string) {
   134  	switch fnName {
   135  	case "Gettext": // Gettext(msgid string) string
   136  		pos := p.fset.Position(x.Pos())
   137  		p.potFile.Messages = append(p.potFile.Messages, po.Message{
   138  			Comment: po.Comment{
   139  				ReferenceFile: []string{p.pkgpath + "/" + filepath.Base(pos.Filename)},
   140  				ReferenceLine: []int{pos.Line},
   141  				Flags:         []string{"go-format"},
   142  			},
   143  			MsgContext: "",
   144  			MsgId:      p.evalStringValue(x.Args[0]),
   145  		})
   146  
   147  	case "PGettext": // PGettext(msgctxt, msgid string) string
   148  		pos := p.fset.Position(x.Pos())
   149  		p.potFile.Messages = append(p.potFile.Messages, po.Message{
   150  			Comment: po.Comment{
   151  				ReferenceFile: []string{p.pkgpath + "/" + filepath.Base(pos.Filename)},
   152  				ReferenceLine: []int{pos.Line},
   153  				Flags:         []string{"go-format"},
   154  			},
   155  			MsgContext: p.evalStringValue(x.Args[0]),
   156  			MsgId:      p.evalStringValue(x.Args[1]),
   157  		})
   158  
   159  	case "NGettext": // NGettext(msgid, msgidPlural string, n int) string
   160  		// TODO
   161  	case "PNGettext": // PNGettext(msgctxt, msgid, msgidPlural string, n int) string
   162  		// TODO
   163  
   164  	case "DGettext": // DGettext(domain, msgid string) string
   165  		// TODO
   166  	case "DPGettext": // DPGettext(domain, msgctxt, msgid string) string
   167  		// TODO
   168  	case "DNGettext": // DNGettext(domain, msgid, msgidPlural string, n int) string
   169  		// TODO
   170  	case "DPNGettext": // DPNGettext(domain, msgctxt, msgid, msgidPlural string, n int) string
   171  		// TODO
   172  	}
   173  }
   174  
   175  func (p *Package) evalStringValue(val interface{}) string {
   176  	switch val.(type) {
   177  	case *ast.BasicLit:
   178  		s := val.(*ast.BasicLit).Value
   179  		s = strings.TrimSpace(s)
   180  		s = strings.Trim(s, `"`+"\n")
   181  		return s
   182  	case *ast.BinaryExpr:
   183  		if val.(*ast.BinaryExpr).Op != token.ADD {
   184  			return ""
   185  		}
   186  		left := p.evalStringValue(val.(*ast.BinaryExpr).X)
   187  		right := p.evalStringValue(val.(*ast.BinaryExpr).Y)
   188  		return left[0:len(left)-1] + right[1:len(right)]
   189  	default:
   190  		panic(fmt.Sprintf("unknown type: %v", val))
   191  	}
   192  }
   193  

View as plain text