cmd/gotext: extract messages in entire binary

Added examples.
Also improved Placeholder name extraction.

Change-Id: I3f271156a1cd364ed4ec71d3e1f8ab8c48568baf
Reviewed-on: https://go-review.googlesource.com/80236
Run-TryBot: Marcel van Lohuizen <mpvl@golang.org>
Reviewed-by: Nigel Tao <nigeltao@golang.org>
diff --git a/cmd/gotext/examples/main.go b/cmd/gotext/examples/extract/main.go
similarity index 100%
rename from cmd/gotext/examples/main.go
rename to cmd/gotext/examples/extract/main.go
diff --git a/cmd/gotext/examples/textdata/gotext_en.out.json b/cmd/gotext/examples/extract/textdata/gotext_en.out.json
similarity index 83%
rename from cmd/gotext/examples/textdata/gotext_en.out.json
rename to cmd/gotext/examples/extract/textdata/gotext_en.out.json
index 3dd943a..2b9124c 100755
--- a/cmd/gotext/examples/textdata/gotext_en.out.json
+++ b/cmd/gotext/examples/extract/textdata/gotext_en.out.json
@@ -6,7 +6,7 @@
         "message": {
             "msg": "Hello world!\n"
         },
-        "position": "golang.org/x/text/cmd/gotext/examples/main.go:27:10"
+        "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:27:10"
     },
     {
         "key": [
@@ -25,7 +25,7 @@
                 "expr": "city"
             }
         ],
-        "position": "golang.org/x/text/cmd/gotext/examples/main.go:31:10"
+        "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:31:10"
     },
     {
         "key": [
@@ -45,7 +45,7 @@
                 "comment": "Town"
             }
         ],
-        "position": "golang.org/x/text/cmd/gotext/examples/main.go:35:10"
+        "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:35:10"
     },
     {
         "key": [
@@ -74,7 +74,7 @@
                 "comment": "Place the person is visiting."
             }
         ],
-        "position": "golang.org/x/text/cmd/gotext/examples/main.go:40:10"
+        "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:40:10"
     },
     {
         "key": [
@@ -111,18 +111,18 @@
                 "expr": "pp.extra"
             }
         ],
-        "position": "golang.org/x/text/cmd/gotext/examples/main.go:55:10"
+        "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:55:10"
     },
     {
         "key": [
             "%d files remaining!"
         ],
         "message": {
-            "msg": "{2} files remaining!"
+            "msg": "{} files remaining!"
         },
         "placeholders": [
             {
-                "id": "2",
+                "id": "",
                 "string": "%[1]d",
                 "type": "int",
                 "underlyingType": "int",
@@ -130,7 +130,7 @@
                 "expr": "2"
             }
         ],
-        "position": "golang.org/x/text/cmd/gotext/examples/main.go:62:10"
+        "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:62:10"
     },
     {
         "key": [
@@ -149,7 +149,7 @@
                 "expr": "n"
             }
         ],
-        "position": "golang.org/x/text/cmd/gotext/examples/main.go:67:10"
+        "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:67:10"
     },
     {
         "key": [
@@ -162,13 +162,13 @@
             {
                 "id": "ReferralCode",
                 "string": "%[1]d",
-                "type": "golang.org/x/text/cmd/gotext/examples.referralCode",
+                "type": "golang.org/x/text/cmd/gotext/examples/extract.referralCode",
                 "underlyingType": "int",
                 "argNum": 1,
                 "expr": "c"
             }
         ],
-        "position": "golang.org/x/text/cmd/gotext/examples/main.go:73:10"
+        "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:73:10"
     },
     {
         "key": [
@@ -189,7 +189,7 @@
                 "expr": "device"
             }
         ],
-        "position": "golang.org/x/text/cmd/gotext/examples/main.go:81:10"
+        "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:81:10"
     },
     {
         "key": [
@@ -216,6 +216,6 @@
                 "expr": "miles"
             }
         ],
-        "position": "golang.org/x/text/cmd/gotext/examples/main.go:85:10"
+        "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:85:10"
     }
 ]
\ No newline at end of file
diff --git a/cmd/gotext/examples/extract_http/main.go b/cmd/gotext/examples/extract_http/main.go
new file mode 100644
index 0000000..4f7484f
--- /dev/null
+++ b/cmd/gotext/examples/extract_http/main.go
@@ -0,0 +1,17 @@
+// Copyright 2017 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
+
+//go:generate gotext extract
+
+import (
+	"net/http"
+
+	"golang.org/x/text/cmd/gotext/examples/extract_http/pkg"
+)
+
+func main() {
+	http.Handle("/generize", http.HandlerFunc(pkg.Generize))
+}
diff --git a/cmd/gotext/examples/extract_http/pkg/pkg.go b/cmd/gotext/examples/extract_http/pkg/pkg.go
new file mode 100644
index 0000000..56bb474
--- /dev/null
+++ b/cmd/gotext/examples/extract_http/pkg/pkg.go
@@ -0,0 +1,27 @@
+// Copyright 2017 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 pkg
+
+import (
+	"net/http"
+
+	"golang.org/x/text/language"
+	"golang.org/x/text/message"
+)
+
+var matcher = language.NewMatcher(message.DefaultCatalog.Languages())
+
+func Generize(w http.ResponseWriter, r *http.Request) {
+	lang, _ := r.Cookie("lang")
+	accept := r.Header.Get("Accept-Language")
+	fallback := "en"
+	tag, _ := language.MatchStrings(matcher, lang.String(), accept, fallback)
+	p := message.NewPrinter(tag)
+
+	p.Fprintf(w, "Hello %s!\n", r.Header.Get("From"))
+
+	p.Fprintf(w, "Do you like your browser (%s)?\n", r.Header.Get("User-Agent"))
+
+}
diff --git a/cmd/gotext/examples/extract_http/textdata/gotext_en.out.json b/cmd/gotext/examples/extract_http/textdata/gotext_en.out.json
new file mode 100755
index 0000000..e6b9968
--- /dev/null
+++ b/cmd/gotext/examples/extract_http/textdata/gotext_en.out.json
@@ -0,0 +1,40 @@
+[
+    {
+        "key": [
+            "Hello %s!\n"
+        ],
+        "message": {
+            "msg": "Hello {From}!\n"
+        },
+        "placeholders": [
+            {
+                "id": "From",
+                "string": "%[1]s",
+                "type": "string",
+                "underlyingType": "string",
+                "argNum": 1,
+                "expr": "r.Header.Get(\"From\")"
+            }
+        ],
+        "position": "golang.org/x/text/cmd/gotext/examples/extract_http/pkg/pkg.go:23:11"
+    },
+    {
+        "key": [
+            "Do you like your browser (%s)?\n"
+        ],
+        "message": {
+            "msg": "Do you like your browser ({User_Agent})?\n"
+        },
+        "placeholders": [
+            {
+                "id": "User_Agent",
+                "string": "%[1]s",
+                "type": "string",
+                "underlyingType": "string",
+                "argNum": 1,
+                "expr": "r.Header.Get(\"User-Agent\")"
+            }
+        ],
+        "position": "golang.org/x/text/cmd/gotext/examples/extract_http/pkg/pkg.go:25:11"
+    }
+]
\ No newline at end of file
diff --git a/cmd/gotext/extract.go b/cmd/gotext/extract.go
index 3b6120d..b7ca559 100644
--- a/cmd/gotext/extract.go
+++ b/cmd/gotext/extract.go
@@ -70,7 +70,7 @@
 
 	var messages []Message
 
-	for _, info := range iprog.InitialPackages() {
+	for _, info := range iprog.AllPackages {
 		for _, f := range info.Files {
 			// Associate comments with nodes.
 			cmap := ast.NewCommentMap(iprog.Fset, f, f.Comments)
@@ -259,6 +259,7 @@
 
 func getID(arg *argument) string {
 	s := getLastComponent(arg.Expr)
+	s = strip(s)
 	s = strings.Replace(s, " ", "", -1)
 	// For small variable names, use user-defined types for more info.
 	if len(s) <= 2 && arg.UnderlyingType != arg.Type {
@@ -267,6 +268,29 @@
 	return strings.Title(s)
 }
 
+// strip is a dirty hack to convert function calls to placeholder IDs.
+func strip(s string) string {
+	s = strings.Map(func(r rune) rune {
+		if unicode.IsSpace(r) || r == '-' {
+			return '_'
+		}
+		if !unicode.In(r, unicode.Letter, unicode.Mark) {
+			return -1
+		}
+		return r
+	}, s)
+	// Strip "Get" from getter functions.
+	if strings.HasPrefix(s, "Get") || strings.HasPrefix(s, "get") {
+		if len(s) > len("get") {
+			r, _ := utf8.DecodeRuneInString(s)
+			if !unicode.In(r, unicode.Ll, unicode.M) { // not lower or mark
+				s = s[len("get"):]
+			}
+		}
+	}
+	return s
+}
+
 type placeholders struct {
 	index map[string]string
 	slice []Placeholder
diff --git a/cmd/gotext/message.go b/cmd/gotext/message.go
index 567cf5d..eaa9005 100644
--- a/cmd/gotext/message.go
+++ b/cmd/gotext/message.go
@@ -106,7 +106,7 @@
 // Select selects a Text based on the feature value associated with a feature of
 // a certain argument.
 type Select struct {
-	Feature string          `json:"feature"` // Name of variable or Feature type
+	Feature string          `json:"feature"` // Name of Feature type (e.g plural)
 	Arg     string          `json:"arg"`     // The placeholder ID
 	Cases   map[string]Text `json:"cases"`
 }