1package main 2 3import ( 4 "bytes" 5 "crypto/md5" 6 "encoding/hex" 7 "encoding/json" 8 "fmt" 9 "go/ast" 10 "go/token" 11 "io" 12 "io/fs" 13 "io/ioutil" 14 "log" 15 "os" 16 "os/exec" 17 "path/filepath" 18 "regexp" 19 "sort" 20 "strings" 21 22 "golang.org/x/tools/go/packages" 23) 24 25const header = ` 26// This file is autogenerated. 27package localescompressed 28` 29 30func main() { 31 createTranslatorsMap() 32 createCurrenciesMap() 33} 34 35func createTranslatorsMap() { 36 const localeMod = "github.com/gohugoio/locales" 37 b := &bytes.Buffer{} 38 cmd := exec.Command("go", "list", "-m", "-json", localeMod) 39 cmd.Stdout = b 40 41 if err := cmd.Run(); err != nil { 42 log.Fatal(err) 43 } 44 45 m := make(map[string]interface{}) 46 if err := json.Unmarshal(b.Bytes(), &m); err != nil { 47 log.Fatal(err) 48 } 49 50 dir := m["Dir"].(string) 51 52 var packageNames []string 53 54 filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { 55 if !d.IsDir() || d.Name() == "currency" { 56 return nil 57 } 58 59 name := filepath.Base(path) 60 61 if _, err := os.Stat(filepath.Join(path, fmt.Sprintf("%s.go", name))); err == nil { 62 packageNames = append(packageNames, name) 63 } 64 65 return nil 66 }) 67 68 sort.Strings(packageNames) 69 70 cfg := &packages.Config{Mode: packages.NeedSyntax | packages.NeedFiles} 71 f, err := os.Create(filepath.Join("..", "locales.autogen.go")) 72 if err != nil { 73 log.Fatal(err) 74 } 75 defer f.Close() 76 77 fmt.Fprint(f, header) 78 fmt.Fprintf(f, "import(\n\"math\"\n\"time\"\n\"strconv\"\n\"github.com/gohugoio/locales\"\n\"github.com/gohugoio/locales/currency\"\n)\n\n") 79 80 var ( 81 allFields []string 82 methodSet = make(map[string]bool) 83 initW = &bytes.Buffer{} 84 counter = 0 85 ) 86 87 for _, k := range packageNames { 88 counter++ 89 90 if counter%50 == 0 { 91 fmt.Printf("[%d] Handling locale %s ...\n", counter, k) 92 } 93 94 pkgs, err := packages.Load(cfg, fmt.Sprintf("github.com/gohugoio/locales/%s", k)) 95 if err != nil { 96 log.Fatal(err) 97 } 98 99 pkg := pkgs[0] 100 gf := pkg.GoFiles[0] 101 102 src, err := ioutil.ReadFile(gf) 103 if err != nil { 104 log.Fatal(err) 105 } 106 107 collector := &coll{src: src, locale: k, w: f, initW: initW, methodSet: methodSet} 108 109 for _, f := range pkg.Syntax { 110 for _, node := range f.Decls { 111 ast.Inspect(node, collector.collectFields) 112 } 113 } 114 115 collector.fields = uniqueStringsSorted(collector.fields) 116 collector.methods = uniqueStringsSorted(collector.methods) 117 allFields = append(allFields, collector.fields...) 118 allFields = uniqueStringsSorted(allFields) 119 120 for _, f := range pkg.Syntax { 121 for _, node := range f.Decls { 122 ast.Inspect(node, collector.collectNew) 123 } 124 } 125 126 } 127 128 fmt.Fprint(f, "\ntype localen struct {\n") 129 for _, field := range allFields { 130 fmt.Fprintf(f, "\t%s\n", field) 131 } 132 133 fmt.Fprint(f, "\n}\n") 134 135 fmt.Fprint(f, "func init() {\n") 136 fmt.Fprint(f, initW.String()) 137 fmt.Fprint(f, "\n}") 138} 139 140type coll struct { 141 // Global 142 w io.Writer 143 initW io.Writer 144 methodSet map[string]bool 145 146 // Local 147 locale string 148 fields []string 149 methods []string 150 src []byte 151} 152 153func (c *coll) collectFields(node ast.Node) bool { 154 switch vv := node.(type) { 155 156 case *ast.FuncDecl: 157 if vv.Recv != nil { 158 recName := vv.Recv.List[0].Names[0].Name 159 name := vv.Name.Name 160 161 start := vv.Pos() - 1 162 end := vv.End() 163 164 body := string(c.src[start : end-1]) 165 re := regexp.MustCompile(`\b` + recName + `\.`) 166 body = re.ReplaceAllString(body, " ln.") 167 body = body[strings.Index(body, name):] 168 hash := toMd5(body) 169 body = body[len(name)+1:] 170 fullName := name + "_" + hash 171 172 sig := body[:strings.Index(body, "{")] 173 funcSig := "func(ln *localen" 174 if !strings.HasPrefix(sig, ")") { 175 funcSig += ", " 176 } 177 field := fmt.Sprintf("fn%s %s%s", name, funcSig, sig) 178 method := fmt.Sprintf("fn%s fn%s", name, fullName) 179 180 c.methods = append(c.methods, method) 181 c.fields = append(c.fields, field) 182 183 body = "var fn" + fullName + " = " + funcSig + body + "\n" 184 185 if !c.methodSet[hash] { 186 fmt.Fprint(c.w, body) 187 } 188 c.methodSet[hash] = true 189 } 190 case *ast.GenDecl: 191 for _, spec := range vv.Specs { 192 switch spec.(type) { 193 case *ast.TypeSpec: 194 typeSpec := spec.(*ast.TypeSpec) 195 switch typeSpec.Type.(type) { 196 case *ast.StructType: 197 structType := typeSpec.Type.(*ast.StructType) 198 for _, field := range structType.Fields.List { 199 typeExpr := field.Type 200 201 start := typeExpr.Pos() - 1 202 end := typeExpr.End() - 1 203 204 typeInSource := c.src[start:end] 205 206 c.fields = append(c.fields, fmt.Sprintf("%s %s", field.Names[0].Name, string(typeInSource))) 207 } 208 } 209 } 210 } 211 212 } 213 214 return false 215} 216 217var returnStructRe = regexp.MustCompile(`return &.*{`) 218 219func (c *coll) collectNew(node ast.Node) bool { 220 switch vv := node.(type) { 221 case *ast.FuncDecl: 222 if vv.Name.Name == "New" { 223 start := vv.Body.Pos() - 1 224 end := vv.Body.End() - 1 225 226 body := string(c.src[start : end-1]) 227 228 body = strings.Replace(body, "{\n\t", "", 1) 229 body = strings.Replace(body, "\t}", "", 1) 230 231 body = returnStructRe.ReplaceAllString(body, "") 232 233 for _, method := range c.methods { 234 if strings.HasPrefix(method, "fn") { 235 parts := strings.Fields(method) 236 body += fmt.Sprintf("\n%s: %s,", parts[0], parts[1]) 237 } 238 } 239 fmt.Fprintf(c.initW, "\ttranslatorFuncs[%q] = %s\n", strings.ToLower(c.locale), fmt.Sprintf("func() locales.Translator {\nreturn &localen{\n%s\n}\n}", body)) 240 } 241 } 242 return false 243} 244 245func uniqueStringsSorted(s []string) []string { 246 if len(s) == 0 { 247 return nil 248 } 249 ss := sort.StringSlice(s) 250 ss.Sort() 251 i := 0 252 for j := 1; j < len(s); j++ { 253 if !ss.Less(i, j) { 254 continue 255 } 256 i++ 257 s[i] = s[j] 258 } 259 260 return s[:i+1] 261} 262 263func toMd5(f string) string { 264 h := md5.New() 265 h.Write([]byte(f)) 266 return hex.EncodeToString(h.Sum([]byte{})) 267} 268 269func createCurrenciesMap() { 270 cfg := &packages.Config{ 271 Mode: packages.LoadSyntax, 272 Tests: false, 273 } 274 275 pkgs, err := packages.Load(cfg, "github.com/gohugoio/locales/currency") 276 if err != nil { 277 log.Fatal(err) 278 } 279 280 pkg := pkgs[0] 281 282 collector := ¤cyCollector{} 283 284 for _, f := range pkg.Syntax { 285 ast.Inspect(f, collector.handleNode) 286 } 287 288 sort.Strings(collector.constants) 289 290 f, err := os.Create(filepath.Join("../currencies.autogen.go")) 291 if err != nil { 292 log.Fatal(err) 293 } 294 defer f.Close() 295 296 fmt.Fprintf(f, "%s\nimport \"github.com/gohugoio/locales/currency\"\n", header) 297 298 fmt.Fprintf(f, "var currencies = map[string]currency.Type {") 299 for _, currency := range collector.constants { 300 fmt.Fprintf(f, "\n%q: currency.%s,", currency, currency) 301 } 302 fmt.Fprintln(f, "}") 303} 304 305type currencyCollector struct { 306 constants []string 307} 308 309func (c *currencyCollector) handleNode(node ast.Node) bool { 310 decl, ok := node.(*ast.GenDecl) 311 if !ok || decl.Tok != token.CONST { 312 return true 313 } 314 typ := "" 315 for _, spec := range decl.Specs { 316 vspec := spec.(*ast.ValueSpec) 317 if vspec.Type == nil && len(vspec.Values) > 0 { 318 typ = "" 319 320 ce, ok := vspec.Values[0].(*ast.CallExpr) 321 if !ok { 322 continue 323 } 324 id, ok := ce.Fun.(*ast.Ident) 325 if !ok { 326 continue 327 } 328 typ = id.Name 329 } 330 if vspec.Type != nil { 331 ident, ok := vspec.Type.(*ast.Ident) 332 if !ok { 333 continue 334 } 335 typ = ident.Name 336 } 337 if typ != "Type" { 338 // This is not the type we're looking for. 339 continue 340 } 341 // We now have a list of names (from one line of source code) all being 342 // declared with the desired type. 343 // Grab their names and actual values and store them in f.values. 344 for _, name := range vspec.Names { 345 c.constants = append(c.constants, name.String()) 346 } 347 } 348 return false 349} 350