summaryrefslogtreecommitdiffstats
path: root/vendor/golang.org/x/text/cmd/gotext/extract.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/golang.org/x/text/cmd/gotext/extract.go')
-rw-r--r--vendor/golang.org/x/text/cmd/gotext/extract.go195
1 files changed, 195 insertions, 0 deletions
diff --git a/vendor/golang.org/x/text/cmd/gotext/extract.go b/vendor/golang.org/x/text/cmd/gotext/extract.go
new file mode 100644
index 000000000..79a9b596d
--- /dev/null
+++ b/vendor/golang.org/x/text/cmd/gotext/extract.go
@@ -0,0 +1,195 @@
+// Copyright 2016 The Go Authors. 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 (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "go/ast"
+ "go/build"
+ "go/constant"
+ "go/format"
+ "go/parser"
+ "go/types"
+ "io/ioutil"
+ "os"
+ "path"
+ "path/filepath"
+ "strings"
+
+ "golang.org/x/tools/go/loader"
+)
+
+// TODO:
+// - merge information into existing files
+// - handle different file formats (PO, XLIFF)
+// - handle features (gender, plural)
+// - message rewriting
+
+var cmdExtract = &Command{
+ Run: runExtract,
+ UsageLine: "extract <package>*",
+ Short: "extract strings to be translated from code",
+}
+
+func runExtract(cmd *Command, args []string) error {
+ if len(args) == 0 {
+ args = []string{"."}
+ }
+
+ conf := loader.Config{
+ Build: &build.Default,
+ ParserMode: parser.ParseComments,
+ }
+
+ // Use the initial packages from the command line.
+ args, err := conf.FromArgs(args, false)
+ if err != nil {
+ return err
+ }
+
+ // Load, parse and type-check the whole program.
+ iprog, err := conf.Load()
+ if err != nil {
+ return err
+ }
+
+ // print returns Go syntax for the specified node.
+ print := func(n ast.Node) string {
+ var buf bytes.Buffer
+ format.Node(&buf, conf.Fset, n)
+ return buf.String()
+ }
+
+ var translations []Translation
+
+ for _, info := range iprog.InitialPackages() {
+ for _, f := range info.Files {
+ // Associate comments with nodes.
+ cmap := ast.NewCommentMap(iprog.Fset, f, f.Comments)
+ getComment := func(n ast.Node) string {
+ cs := cmap.Filter(n).Comments()
+ if len(cs) > 0 {
+ return strings.TrimSpace(cs[0].Text())
+ }
+ return ""
+ }
+
+ // Find function calls.
+ ast.Inspect(f, func(n ast.Node) bool {
+ call, ok := n.(*ast.CallExpr)
+ if !ok {
+ return true
+ }
+
+ // Skip calls of functions other than
+ // (*message.Printer).{Sp,Fp,P}rintf.
+ sel, ok := call.Fun.(*ast.SelectorExpr)
+ if !ok {
+ return true
+ }
+ meth := info.Selections[sel]
+ if meth == nil || meth.Kind() != types.MethodVal {
+ return true
+ }
+ // TODO: remove cheap hack and check if the type either
+ // implements some interface or is specifically of type
+ // "golang.org/x/text/message".Printer.
+ m, ok := extractFuncs[path.Base(meth.Recv().String())]
+ if !ok {
+ return true
+ }
+
+ // argn is the index of the format string.
+ argn, ok := m[meth.Obj().Name()]
+ if !ok || argn >= len(call.Args) {
+ return true
+ }
+
+ // Skip calls with non-constant format string.
+ fmtstr := info.Types[call.Args[argn]].Value
+ if fmtstr == nil || fmtstr.Kind() != constant.String {
+ return true
+ }
+
+ posn := conf.Fset.Position(call.Lparen)
+ filepos := fmt.Sprintf("%s:%d:%d", filepath.Base(posn.Filename), posn.Line, posn.Column)
+
+ // TODO: identify the type of the format argument. If it is not
+ // a string, multiple keys may be defined.
+ var key []string
+
+ // TODO: replace substitutions (%v) with a translator friendly
+ // notation. For instance:
+ // "%d files remaining" -> "{numFiles} files remaining", or
+ // "%d files remaining" -> "{arg1} files remaining"
+ // Alternatively, this could be done at a later stage.
+ msg := constant.StringVal(fmtstr)
+
+ // Construct a Translation unit.
+ c := Translation{
+ Key: key,
+ Position: filepath.Join(info.Pkg.Path(), filepos),
+ Original: Text{Msg: msg},
+ ExtractedComment: getComment(call.Args[0]),
+ // TODO(fix): this doesn't get the before comment.
+ // Comment: getComment(call),
+ }
+
+ for i, arg := range call.Args[argn+1:] {
+ var val string
+ if v := info.Types[arg].Value; v != nil {
+ val = v.ExactString()
+ }
+ posn := conf.Fset.Position(arg.Pos())
+ filepos := fmt.Sprintf("%s:%d:%d", filepath.Base(posn.Filename), posn.Line, posn.Column)
+ c.Args = append(c.Args, Argument{
+ ID: i + 1,
+ Type: info.Types[arg].Type.String(),
+ UnderlyingType: info.Types[arg].Type.Underlying().String(),
+ Expr: print(arg),
+ Value: val,
+ Comment: getComment(arg),
+ Position: filepath.Join(info.Pkg.Path(), filepos),
+ // TODO report whether it implements
+ // interfaces plural.Interface,
+ // gender.Interface.
+ })
+ }
+
+ translations = append(translations, c)
+ return true
+ })
+ }
+ }
+
+ data, err := json.MarshalIndent(translations, "", " ")
+ if err != nil {
+ return err
+ }
+ for _, tag := range getLangs() {
+ // TODO: merge with existing files, don't overwrite.
+ os.MkdirAll(*dir, 0744)
+ file := filepath.Join(*dir, fmt.Sprintf("gotext_%v.out.json", tag))
+ if err := ioutil.WriteFile(file, data, 0744); err != nil {
+ return fmt.Errorf("could not create file: %v", err)
+ }
+ }
+ return nil
+}
+
+// extractFuncs indicates the types and methods for which to extract strings,
+// and which argument to extract.
+// TODO: use the types in conf.Import("golang.org/x/text/message") to extract
+// the correct instances.
+var extractFuncs = map[string]map[string]int{
+ // TODO: Printer -> *golang.org/x/text/message.Printer
+ "message.Printer": {
+ "Printf": 0,
+ "Sprintf": 0,
+ "Fprintf": 1,
+ },
+}