| // Copyright (C) 2016 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package main |
| |
| import ( |
| "flag" |
| "fmt" |
| "io/ioutil" |
| "os" |
| "path/filepath" |
| "reflect" |
| "sort" |
| |
| "android.googlesource.com/platform/tools/gpu/framework/app" |
| "android.googlesource.com/platform/tools/gpu/framework/binary/cyclic" |
| "android.googlesource.com/platform/tools/gpu/framework/binary/endian" |
| "android.googlesource.com/platform/tools/gpu/framework/device" |
| "android.googlesource.com/platform/tools/gpu/framework/log" |
| "android.googlesource.com/platform/tools/gpu/framework/stringtable" |
| "android.googlesource.com/platform/tools/gpu/framework/stringtable/parser" |
| ) |
| |
| var ( |
| def = flag.String("def", "", "The path to the Go string definition file") |
| pkg = flag.String("pkg", "", "The directory to hold the output string packages.") |
| ) |
| |
| func main() { |
| app.ShortHelp = "stringgen compiles string table files to string packages and a Go definition file." |
| app.Run(run) |
| } |
| |
| func run(ctx log.Context) error { |
| tables := map[stringtable.Info]*stringtable.StringTable{} |
| |
| for _, path := range flag.Args() { |
| content, err := ioutil.ReadFile(path) |
| if err != nil { |
| return err |
| } |
| table, errs := parser.Parse(path, string(content)) |
| if len(errs) > 0 { |
| return fmt.Errorf("%s", errs) |
| } |
| if existing, exists := tables[table.Info]; exists { |
| // Merge tables, check for duplicates. |
| for k, v := range table.Entries { |
| if _, dup := existing.Entries[k]; !dup { |
| existing.Entries[k] = v |
| } else { |
| return ctx.S("Key", k).AsError("Duplicate string key found") |
| } |
| } |
| } else { |
| tables[table.Info] = table |
| } |
| } |
| |
| if len(tables) == 0 { |
| return ctx.AsError("No string table files provided") |
| } |
| |
| ctx.Printf("Found %d string table file(s)", len(tables)) |
| |
| if err := validate(ctx, tables); err != nil { |
| return err |
| } |
| |
| if *pkg != "" { |
| if err := writePackages(tables, *pkg); err != nil { |
| return err |
| } |
| } |
| |
| if *def != "" { |
| for _, t := range tables { |
| if err := writeDefinitions(t, *def); err != nil { |
| return err |
| } |
| break |
| } |
| } |
| |
| return nil |
| } |
| |
| // entry is a map of Info -> parameter list |
| type entry map[stringtable.Info][]string |
| |
| func writePackages(tables map[stringtable.Info]*stringtable.StringTable, path string) error { |
| for info, table := range tables { |
| path := filepath.Join(path, info.CultureCode+".stb") |
| os.MkdirAll(filepath.Dir(path), 0755) |
| w, err := os.Create(path) |
| if err != nil { |
| return err |
| } |
| defer w.Close() |
| |
| e := cyclic.Encoder(endian.Writer(w, device.LittleEndian)) |
| e.Object(table) |
| if err := e.Error(); err != nil { |
| return err |
| } |
| } |
| return nil |
| } |
| |
| func writeDefinitions(table *stringtable.StringTable, path string) error { |
| abspath, err := filepath.Abs(path) |
| if err != nil { |
| return err |
| } |
| os.MkdirAll(filepath.Dir(abspath), 0755) |
| w, err := os.Create(abspath) |
| if err != nil { |
| return err |
| } |
| defer w.Close() |
| |
| entries := make(EntryList, 0, len(table.Entries)) |
| for key, node := range table.Entries { |
| entry := Entry{ |
| Key: key, |
| Parameters: params(node), |
| } |
| entries = append(entries, entry) |
| } |
| |
| sort.Sort(entries) |
| |
| _, pkg := filepath.Split(filepath.Dir(abspath)) |
| return Execute(pkg, entries, w) |
| } |
| |
| // checks for consistency between the various localizations of strings. |
| func validate(ctx log.Context, tables map[stringtable.Info]*stringtable.StringTable) error { |
| all := map[string]entry{} |
| |
| for info, table := range tables { |
| for key, node := range table.Entries { |
| e := all[key] |
| if e == nil { |
| e = make(entry) |
| } |
| e[info] = params(node) |
| all[key] = e |
| } |
| } |
| |
| for key, entry := range all { |
| ctx := ctx.S("Key", key) |
| if len(entry) != len(tables) { |
| for info := range tables { |
| if _, found := entry[info]; !found { |
| return ctx.V("Table", info).AsError("StringTable missing entry") |
| } |
| } |
| } |
| var firstInfo stringtable.Info |
| var firstParams []string |
| for info, params := range entry { |
| if firstParams == nil { |
| firstInfo, firstParams = info, params |
| } else { |
| if reflect.DeepEqual(firstParams, params) { |
| return ctx.V("First", firstInfo).V("Second", info).AsError("Parameter list different") |
| } |
| } |
| } |
| } |
| |
| return nil |
| } |
| |
| // params returns the list of parameters used by the stringtable node. |
| func params(n stringtable.Node) []string { |
| switch n := n.(type) { |
| case *stringtable.Block: |
| p := []string{} |
| for _, n := range n.Children { |
| p = append(p, params(n)...) |
| } |
| return p |
| case *stringtable.Parameter: |
| return []string{n.Key} |
| } |
| return nil |
| } |