OSDN Git Service

new repo
[bytom/vapor.git] / vendor / golang.org / x / text / cmd / gotext / extract.go
1 // Copyright 2016 The Go Authors. 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         "bytes"
9         "encoding/json"
10         "fmt"
11         "go/ast"
12         "go/build"
13         "go/constant"
14         "go/format"
15         "go/parser"
16         "go/types"
17         "io/ioutil"
18         "os"
19         "path"
20         "path/filepath"
21         "strings"
22
23         "golang.org/x/tools/go/loader"
24 )
25
26 // TODO:
27 // - merge information into existing files
28 // - handle different file formats (PO, XLIFF)
29 // - handle features (gender, plural)
30 // - message rewriting
31
32 var cmdExtract = &Command{
33         Run:       runExtract,
34         UsageLine: "extract <package>*",
35         Short:     "extract strings to be translated from code",
36 }
37
38 func runExtract(cmd *Command, args []string) error {
39         if len(args) == 0 {
40                 args = []string{"."}
41         }
42
43         conf := loader.Config{
44                 Build:      &build.Default,
45                 ParserMode: parser.ParseComments,
46         }
47
48         // Use the initial packages from the command line.
49         args, err := conf.FromArgs(args, false)
50         if err != nil {
51                 return err
52         }
53
54         // Load, parse and type-check the whole program.
55         iprog, err := conf.Load()
56         if err != nil {
57                 return err
58         }
59
60         // print returns Go syntax for the specified node.
61         print := func(n ast.Node) string {
62                 var buf bytes.Buffer
63                 format.Node(&buf, conf.Fset, n)
64                 return buf.String()
65         }
66
67         var translations []Translation
68
69         for _, info := range iprog.InitialPackages() {
70                 for _, f := range info.Files {
71                         // Associate comments with nodes.
72                         cmap := ast.NewCommentMap(iprog.Fset, f, f.Comments)
73                         getComment := func(n ast.Node) string {
74                                 cs := cmap.Filter(n).Comments()
75                                 if len(cs) > 0 {
76                                         return strings.TrimSpace(cs[0].Text())
77                                 }
78                                 return ""
79                         }
80
81                         // Find function calls.
82                         ast.Inspect(f, func(n ast.Node) bool {
83                                 call, ok := n.(*ast.CallExpr)
84                                 if !ok {
85                                         return true
86                                 }
87
88                                 // Skip calls of functions other than
89                                 // (*message.Printer).{Sp,Fp,P}rintf.
90                                 sel, ok := call.Fun.(*ast.SelectorExpr)
91                                 if !ok {
92                                         return true
93                                 }
94                                 meth := info.Selections[sel]
95                                 if meth == nil || meth.Kind() != types.MethodVal {
96                                         return true
97                                 }
98                                 // TODO: remove cheap hack and check if the type either
99                                 // implements some interface or is specifically of type
100                                 // "golang.org/x/text/message".Printer.
101                                 m, ok := extractFuncs[path.Base(meth.Recv().String())]
102                                 if !ok {
103                                         return true
104                                 }
105
106                                 // argn is the index of the format string.
107                                 argn, ok := m[meth.Obj().Name()]
108                                 if !ok || argn >= len(call.Args) {
109                                         return true
110                                 }
111
112                                 // Skip calls with non-constant format string.
113                                 fmtstr := info.Types[call.Args[argn]].Value
114                                 if fmtstr == nil || fmtstr.Kind() != constant.String {
115                                         return true
116                                 }
117
118                                 posn := conf.Fset.Position(call.Lparen)
119                                 filepos := fmt.Sprintf("%s:%d:%d", filepath.Base(posn.Filename), posn.Line, posn.Column)
120
121                                 // TODO: identify the type of the format argument. If it is not
122                                 // a string, multiple keys may be defined.
123                                 var key []string
124
125                                 // TODO: replace substitutions (%v) with a translator friendly
126                                 // notation. For instance:
127                                 //     "%d files remaining" -> "{numFiles} files remaining", or
128                                 //     "%d files remaining" -> "{arg1} files remaining"
129                                 // Alternatively, this could be done at a later stage.
130                                 msg := constant.StringVal(fmtstr)
131
132                                 // Construct a Translation unit.
133                                 c := Translation{
134                                         Key:              key,
135                                         Position:         filepath.Join(info.Pkg.Path(), filepos),
136                                         Original:         Text{Msg: msg},
137                                         ExtractedComment: getComment(call.Args[0]),
138                                         // TODO(fix): this doesn't get the before comment.
139                                         // Comment: getComment(call),
140                                 }
141
142                                 for i, arg := range call.Args[argn+1:] {
143                                         var val string
144                                         if v := info.Types[arg].Value; v != nil {
145                                                 val = v.ExactString()
146                                         }
147                                         posn := conf.Fset.Position(arg.Pos())
148                                         filepos := fmt.Sprintf("%s:%d:%d", filepath.Base(posn.Filename), posn.Line, posn.Column)
149                                         c.Args = append(c.Args, Argument{
150                                                 ID:             i + 1,
151                                                 Type:           info.Types[arg].Type.String(),
152                                                 UnderlyingType: info.Types[arg].Type.Underlying().String(),
153                                                 Expr:           print(arg),
154                                                 Value:          val,
155                                                 Comment:        getComment(arg),
156                                                 Position:       filepath.Join(info.Pkg.Path(), filepos),
157                                                 // TODO report whether it implements
158                                                 // interfaces plural.Interface,
159                                                 // gender.Interface.
160                                         })
161                                 }
162
163                                 translations = append(translations, c)
164                                 return true
165                         })
166                 }
167         }
168
169         data, err := json.MarshalIndent(translations, "", "    ")
170         if err != nil {
171                 return err
172         }
173         for _, tag := range getLangs() {
174                 // TODO: merge with existing files, don't overwrite.
175                 os.MkdirAll(*dir, 0744)
176                 file := filepath.Join(*dir, fmt.Sprintf("gotext_%v.out.json", tag))
177                 if err := ioutil.WriteFile(file, data, 0744); err != nil {
178                         return fmt.Errorf("could not create file: %v", err)
179                 }
180         }
181         return nil
182 }
183
184 // extractFuncs indicates the types and methods for which to extract strings,
185 // and which argument to extract.
186 // TODO: use the types in conf.Import("golang.org/x/text/message") to extract
187 // the correct instances.
188 var extractFuncs = map[string]map[string]int{
189         // TODO: Printer -> *golang.org/x/text/message.Printer
190         "message.Printer": {
191                 "Printf":  0,
192                 "Sprintf": 0,
193                 "Fprintf": 1,
194         },
195 }