From 75a3d22c53fe5f4bddd38d76b954e11a608631bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20B=C3=B6hmke?= Date: Sat, 22 Feb 2020 22:35:39 +0100 Subject: [PATCH] initial rework of xgotext --- cli/xgotext/fixtures/main.go | 34 +++ cli/xgotext/fixtures/pkg/pkg.go | 16 ++ cli/xgotext/main.go | 354 ++------------------------------ cli/xgotext/parser/domain.go | 129 ++++++++++++ cli/xgotext/parser/golang.go | 255 +++++++++++++++++++++++ cli/xgotext/parser/parser.go | 55 +++++ go.mod | 4 + go.sum | 14 ++ 8 files changed, 525 insertions(+), 336 deletions(-) create mode 100644 cli/xgotext/fixtures/pkg/pkg.go create mode 100644 cli/xgotext/parser/domain.go create mode 100644 cli/xgotext/parser/golang.go create mode 100644 cli/xgotext/parser/parser.go create mode 100644 go.sum diff --git a/cli/xgotext/fixtures/main.go b/cli/xgotext/fixtures/main.go index 204b330..ffbf91a 100644 --- a/cli/xgotext/fixtures/main.go +++ b/cli/xgotext/fixtures/main.go @@ -4,6 +4,8 @@ import ( "fmt" "github.com/leonelquinteros/gotext" + alias "github.com/leonelquinteros/gotext" + "github.com/leonelquinteros/gotext/cli/xgotext/fixtures/pkg" ) // Fake object with methods similar to gotext @@ -15,12 +17,26 @@ func (f Fake) Get(id int) int { return 42 } +// Fake object with same methods as gotext +type Fake2 struct { +} + +// Get by str +func (f Fake2) Get(s string) string { + return s +} + func main() { // Configure package gotext.Configure("/path/to/locales/root/dir", "en_UK", "domain-name") // Translate text from default domain fmt.Println(gotext.Get("My text on 'domain-name' domain")) + // same as before + fmt.Println(gotext.Get("My text on 'domain-name' domain")) + + // same with alias package name + fmt.Println(alias.Get("alias call")) // Translate text from a different domain without reconfigure fmt.Println(gotext.GetD("domain2", "Another text on a different domain")) @@ -43,6 +59,24 @@ func main() { l.GetDC("domain2", "string", "ctx") l.GetNDC("translations", "ndc", "ndcs", 7, "NDC-CTX") + // try fake structs f := Fake{} f.Get(3) + + f2 := Fake2{} + f2.Get("3") + + // use translator of sub object + t := pkg.Translate{} + t.L.Get("translate package") + t.S.L.Get("translate sub package") + + // redefine alias with fake struct + alias := Fake2{} + alias.Get("3") +} + +// dummy function +func dummy(locale *gotext.Locale) { + locale.Get("inside dummy") } diff --git a/cli/xgotext/fixtures/pkg/pkg.go b/cli/xgotext/fixtures/pkg/pkg.go new file mode 100644 index 0000000..320fb32 --- /dev/null +++ b/cli/xgotext/fixtures/pkg/pkg.go @@ -0,0 +1,16 @@ +package pkg + +import "github.com/leonelquinteros/gotext" + +type SubTranslate struct { + L gotext.Locale +} + +type Translate struct { + L gotext.Locale + S SubTranslate +} + +func test() { + gotext.Get("inside sub package") +} diff --git a/cli/xgotext/main.go b/cli/xgotext/main.go index 80bf9a6..fb1125a 100644 --- a/cli/xgotext/main.go +++ b/cli/xgotext/main.go @@ -2,24 +2,16 @@ package main import ( "flag" - "fmt" - "go/ast" - "go/parser" - "go/token" "log" "os" - "path" - "strconv" + "path/filepath" + + "github.com/leonelquinteros/gotext/cli/xgotext/parser" ) var ( - debug = flag.Bool("debug", false, "enable debug mode and print AST") - dirName = flag.String("in", "", "input dir: /path/to/go/pkg") - outputDir = flag.String("out", "", "output dir: /path/to/i18n/files") - fset *token.FileSet - domainFiles map[string]*os.File - currentDomain = "default" - currentFile string + dirName = flag.String("in", "", "input dir: /path/to/go/pkg") + outputDir = flag.String("out", "", "output dir: /path/to/i18n/files") ) func main() { @@ -28,44 +20,24 @@ func main() { // Init logger log.SetFlags(0) - // Init domain files - domainFiles = make(map[string]*os.File) - - // Check if dir name parameter is valid - log.Println(*dirName) - f, err := os.Stat(*dirName) + data, err := parser.ParseDirRec(*dirName) if err != nil { log.Fatal(err) } - // Process file or dir - if f.IsDir() { - parseDir(*dirName) - } else { - parseFile(*dirName) - } -} - -func getDomainFile(domain string) *os.File { - // Return existent when available - if f, ok := domainFiles[domain]; ok { - return f - } - - // If the file doesn't exist, create it. - filePath := path.Join(*outputDir, domain+".po") - f, err := os.OpenFile(filePath, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0644) + err = os.MkdirAll(*outputDir, os.ModePerm) if err != nil { - log.Fatal(err) + log.Fatalf("failed to create output dir: %s", err) } - domainFiles[domain] = f - writePoHeader(f) - return f -} + for name, domain := range data { + outFile := filepath.Join(*outputDir, name+".po") + file, err := os.Create(outFile) + if err != nil { + log.Fatalf("failed to save po file for %s: %s", name, err) + } -func writePoHeader(f *os.File) { - h := `msgid "" + file.WriteString(`msgid "" msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "MIME-Version: 1.0\n" @@ -74,298 +46,8 @@ msgstr "" "Language: \n" "X-Generator: xgotext\n" - ` - f.Write([]byte(h)) -} - -func write(dom, msgid string) { - f := getDomainFile(dom) - f.Write([]byte("\nmsgid " + msgid)) - f.Write([]byte("\nmsgstr \"\"")) - f.Write([]byte("\n")) -} - -func writePlural(dom, msgid, msgidPlural string) { - f := getDomainFile(dom) - f.Write([]byte("\nmsgid " + msgid)) - f.Write([]byte("\nmsgid_plural " + msgidPlural)) - f.Write([]byte("\nmsgstr[0] \"\"")) - f.Write([]byte("\nmsgstr[1] \"\"")) - f.Write([]byte("\n")) -} - -func writeContext(dom, ctx string) { - f := getDomainFile(dom) - f.Write([]byte("\nmsgctxt " + ctx)) -} - -func writeComments(dom, file, call string) { - f := getDomainFile(dom) - f.Write([]byte("\n#: " + file)) - f.Write([]byte("\n#. " + call)) -} - -func parseDir(dirName string) error { - fset := token.NewFileSet() - pkgs, err := parser.ParseDir(fset, dirName, nil, parser.AllErrors) - if err != nil { - log.Fatal(err) - } - - for _, pkg := range pkgs { - for fn := range pkg.Files { - parseFile(fn) - } - } - - return nil -} - -func parseFile(fileName string) error { - // Remember current file to write comments on .po file - currentFile = fileName - - // Parse AST - fset = token.NewFileSet() - node, err := parser.ParseFile(fset, fileName, nil, parser.AllErrors) - if err != nil { - log.Fatal(err) - return err - } - - // Debug mode - if *debug { - ast.Print(fset, node) - } - - ast.Inspect(node, inspectFile) - - return nil -} - -func inspectFile(n ast.Node) bool { - switch x := n.(type) { - case *ast.CallExpr: - inspectCallExpr(x) - } - - return true -} - -func inspectCallExpr(n *ast.CallExpr) { - if se, ok := n.Fun.(*ast.SelectorExpr); ok { - switch se.Sel.String() { - case "Get": - parseGet(n) - - case "GetN": - parseGetN(n) - - case "GetD": - parseGetD(n) - - case "GetND": - parseGetND(n) - - case "GetC": - parseGetC(n) - - case "GetNC": - parseGetNC(n) - - case "GetDC": - parseGetDC(n) - - case "GetNDC": - parseGetNDC(n) - - case "SetDomain": - parseSetDomain(n) - - } - } -} - -func parseGet(call *ast.CallExpr) { - if call.Args != nil && len(call.Args) > 0 { - if lit, ok := call.Args[0].(*ast.BasicLit); ok { - if lit.Kind == token.STRING { - writeComments(currentDomain, - fmt.Sprintf("%s:%d", fset.Position(call.Lparen).Filename, fset.Position(call.Lparen).Line), - fmt.Sprintf("%s.%s", call.Fun.(*ast.SelectorExpr).X.(*ast.Ident).Name, call.Fun.(*ast.SelectorExpr).Sel.String()), - ) - write(currentDomain, lit.Value) - } - } - } -} - -func parseGetN(call *ast.CallExpr) { - if call.Args == nil || len(call.Args) < 3 { - return - } - - if lit, ok := call.Args[0].(*ast.BasicLit); ok { - if lit1, ok1 := call.Args[1].(*ast.BasicLit); ok1 { - if lit.Kind == token.STRING && lit1.Kind == token.STRING { - switch x := call.Args[2].(type) { - case *ast.BasicLit: - if x.Kind != token.INT { - return - } - - case *ast.Ident: - if x.Obj.Kind != ast.Var && x.Obj.Kind != ast.Con { - return - } - default: - return - } - writeComments(currentDomain, - fmt.Sprintf("%s:%d", fset.Position(call.Lparen).Filename, fset.Position(call.Lparen).Line), - fmt.Sprintf("%s.%s", call.Fun.(*ast.SelectorExpr).X.(*ast.Ident).Name, call.Fun.(*ast.SelectorExpr).Sel.String()), - ) - writePlural(currentDomain, lit.Value, lit1.Value) - } - } - } -} - -func parseGetD(call *ast.CallExpr) { - if call.Args != nil && len(call.Args) > 1 { - if lit, ok := call.Args[0].(*ast.BasicLit); ok { - if lit1, ok := call.Args[1].(*ast.BasicLit); ok { - if lit.Kind == token.STRING && lit1.Kind == token.STRING { - dom, err := strconv.Unquote(lit.Value) - if err != nil { - log.Fatal(err) - } - writeComments(dom, - fmt.Sprintf("%s:%d", fset.Position(call.Lparen).Filename, fset.Position(call.Lparen).Line), - fmt.Sprintf("%s.%s", call.Fun.(*ast.SelectorExpr).X.(*ast.Ident).Name, call.Fun.(*ast.SelectorExpr).Sel.String()), - ) - write(dom, lit1.Value) - } - } - } - } -} - -func parseGetND(call *ast.CallExpr) { - if call.Args != nil && len(call.Args) > 2 { - if lit, ok := call.Args[0].(*ast.BasicLit); ok { - if lit1, ok := call.Args[1].(*ast.BasicLit); ok { - if lit2, ok := call.Args[2].(*ast.BasicLit); ok { - if lit.Kind == token.STRING && lit1.Kind == token.STRING && lit2.Kind == token.STRING { - dom, err := strconv.Unquote(lit.Value) - if err != nil { - log.Fatal(err) - } - writeComments(dom, - fmt.Sprintf("%s:%d", fset.Position(call.Lparen).Filename, fset.Position(call.Lparen).Line), - fmt.Sprintf("%s.%s", call.Fun.(*ast.SelectorExpr).X.(*ast.Ident).Name, call.Fun.(*ast.SelectorExpr).Sel.String()), - ) - writePlural(dom, lit1.Value, lit2.Value) - } - } - } - } - } -} - -func parseGetC(call *ast.CallExpr) { - if call.Args != nil && len(call.Args) > 1 { - if lit, ok := call.Args[0].(*ast.BasicLit); ok { - if lit1, ok := call.Args[1].(*ast.BasicLit); ok { - if lit.Kind == token.STRING && lit1.Kind == token.STRING { - writeComments(currentDomain, - fmt.Sprintf("%s:%d", fset.Position(call.Lparen).Filename, fset.Position(call.Lparen).Line), - fmt.Sprintf("%s.%s", call.Fun.(*ast.SelectorExpr).X.(*ast.Ident).Name, call.Fun.(*ast.SelectorExpr).Sel.String()), - ) - writeContext(currentDomain, lit1.Value) - write(currentDomain, lit.Value) - } - } - } - } -} - -func parseGetNC(call *ast.CallExpr) { - if call.Args != nil && len(call.Args) > 3 { - if lit, ok := call.Args[0].(*ast.BasicLit); ok { - if lit1, ok := call.Args[1].(*ast.BasicLit); ok { - if lit3, ok := call.Args[3].(*ast.BasicLit); ok { - if lit.Kind == token.STRING && lit1.Kind == token.STRING && lit3.Kind == token.STRING { - writeComments(currentDomain, - fmt.Sprintf("%s:%d", fset.Position(call.Lparen).Filename, fset.Position(call.Lparen).Line), - fmt.Sprintf("%s.%s", call.Fun.(*ast.SelectorExpr).X.(*ast.Ident).Name, call.Fun.(*ast.SelectorExpr).Sel.String()), - ) - writeContext(currentDomain, lit3.Value) - writePlural(currentDomain, lit.Value, lit1.Value) - } - } - } - } - } -} - -func parseGetDC(call *ast.CallExpr) { - if call.Args != nil && len(call.Args) > 2 { - if lit, ok := call.Args[0].(*ast.BasicLit); ok { - if lit1, ok := call.Args[1].(*ast.BasicLit); ok { - if lit2, ok := call.Args[2].(*ast.BasicLit); ok { - if lit.Kind == token.STRING && lit1.Kind == token.STRING && lit2.Kind == token.STRING { - dom, err := strconv.Unquote(lit.Value) - if err != nil { - log.Fatal(err) - } - writeComments(dom, - fmt.Sprintf("%s:%d", fset.Position(call.Lparen).Filename, fset.Position(call.Lparen).Line), - fmt.Sprintf("%s.%s", call.Fun.(*ast.SelectorExpr).X.(*ast.Ident).Name, call.Fun.(*ast.SelectorExpr).Sel.String()), - ) - writeContext(dom, lit2.Value) - write(dom, lit1.Value) - } - } - } - } - } -} - -func parseGetNDC(call *ast.CallExpr) { - if call.Args != nil && len(call.Args) > 4 { - if lit, ok := call.Args[0].(*ast.BasicLit); ok { - if lit1, ok := call.Args[1].(*ast.BasicLit); ok { - if lit2, ok := call.Args[2].(*ast.BasicLit); ok { - if lit4, ok := call.Args[4].(*ast.BasicLit); ok { - if lit.Kind == token.STRING && lit1.Kind == token.STRING && lit2.Kind == token.STRING && lit4.Kind == token.STRING { - dom, err := strconv.Unquote(lit.Value) - if err != nil { - log.Fatal(err) - } - writeComments(dom, - fmt.Sprintf("%s:%d", fset.Position(call.Lparen).Filename, fset.Position(call.Lparen).Line), - fmt.Sprintf("%s.%s", call.Fun.(*ast.SelectorExpr).X.(*ast.Ident).Name, call.Fun.(*ast.SelectorExpr).Sel.String()), - ) - writeContext(dom, lit4.Value) - writePlural(dom, lit1.Value, lit2.Value) - } - } - } - } - } - } -} - -func parseSetDomain(call *ast.CallExpr) { - if call.Args != nil && len(call.Args) == 1 { - if lit, ok := call.Args[0].(*ast.BasicLit); ok { - if lit.Kind == token.STRING { - cd, err := strconv.Unquote(lit.Value) - if err == nil { - currentDomain = cd - } - } - } +`) + file.WriteString(domain.Dump()) + file.Close() } } diff --git a/cli/xgotext/parser/domain.go b/cli/xgotext/parser/domain.go new file mode 100644 index 0000000..126031c --- /dev/null +++ b/cli/xgotext/parser/domain.go @@ -0,0 +1,129 @@ +package parser + +import ( + "sort" + "strings" +) + +// Translation for a text to translate +type Translation struct { + MsgId string + MsgIdPlural string + Context string + SourceLocations []string +} + +// AddLocations to translation +func (t *Translation) AddLocations(locations []string) { + if t.SourceLocations == nil { + t.SourceLocations = locations + } else { + t.SourceLocations = append(t.SourceLocations, locations...) + } +} + +// Dump translation as string +func (t *Translation) Dump() string { + data := make([]string, 0, len(t.SourceLocations)+5) + + for _, location := range t.SourceLocations { + data = append(data, "#: "+location) + } + + if t.Context != "" { + data = append(data, "msgctxt "+t.Context) + } + + data = append(data, "msgid "+t.MsgId) + + if t.MsgIdPlural == "" { + data = append(data, "msgstr \"\"") + } else { + data = append(data, + "msgid_plural "+t.MsgIdPlural, + "msgstr[0] \"\"", + "msgstr[1] \"\"") + } + + return strings.Join(data, "\n") +} + +// TranslationMap contains a map of translations with the ID as key +type TranslationMap map[string]*Translation + +// Dump the translation map as string +func (m TranslationMap) Dump() string { + // sort by translation id for consistence output + keys := make([]string, 0, len(m)) + for k := range m { + keys = append(keys, k) + } + sort.Strings(keys) + + data := make([]string, 0, len(m)) + for _, key := range keys { + data = append(data, (m)[key].Dump()) + } + return strings.Join(data, "\n\n") +} + +// Domain holds all translations of one domain +type Domain struct { + Translations TranslationMap + ContextTranslations map[string]TranslationMap +} + +// AddTranslation to the domain +func (d *Domain) AddTranslation(translation *Translation) { + if d.Translations == nil { + d.Translations = make(TranslationMap) + d.ContextTranslations = make(map[string]TranslationMap) + } + + if translation.Context == "" { + if t, ok := d.Translations[translation.MsgId]; ok { + t.AddLocations(translation.SourceLocations) + } else { + d.Translations[translation.MsgId] = translation + } + } else { + if _, ok := d.ContextTranslations[translation.Context]; !ok { + d.ContextTranslations[translation.Context] = make(TranslationMap) + } + + if t, ok := d.ContextTranslations[translation.Context][translation.MsgId]; ok { + t.AddLocations(translation.SourceLocations) + } else { + d.ContextTranslations[translation.Context][translation.MsgId] = translation + } + } +} + +// Dump the domain as string +func (d *Domain) Dump() string { + data := make([]string, 0, len(d.ContextTranslations)+1) + data = append(data, d.Translations.Dump()) + + // sort context translations by context for consistence output + keys := make([]string, 0, len(d.ContextTranslations)) + for k := range d.ContextTranslations { + keys = append(keys, k) + } + sort.Strings(keys) + + for _, key := range keys { + data = append(data, d.ContextTranslations[key].Dump()) + } + return strings.Join(data, "\n\n") +} + +// DomainMap contains multiple domains as map with name as key +type DomainMap map[string]*Domain + +// AddTranslation to domain map +func (m *DomainMap) AddTranslation(domain string, translation *Translation) { + if _, ok := (*m)[domain]; !ok { + (*m)[domain] = new(Domain) + } + (*m)[domain].AddTranslation(translation) +} diff --git a/cli/xgotext/parser/golang.go b/cli/xgotext/parser/golang.go new file mode 100644 index 0000000..0c33d36 --- /dev/null +++ b/cli/xgotext/parser/golang.go @@ -0,0 +1,255 @@ +package parser + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + "log" + "path/filepath" + "strconv" + + "golang.org/x/tools/go/packages" +) + +// GetterDef describes a getter +type GetterDef struct { + Id int + Plural int + Context int + Domain int +} + +// maxArgIndex returns the largest argument index +func (d *GetterDef) maxArgIndex() int { + m := d.Id + if d.Plural > m { + m = d.Plural + } + if d.Context > m { + m = d.Context + } + if d.Domain > m { + m = d.Domain + } + return m +} + +// list of supported getter +var gotextGetter = map[string]GetterDef{ + "Get": {0, -1, -1, -1}, + "GetN": {0, 1, -1, -1}, + "GetD": {1, -1, -1, 0}, + "GetND": {1, 2, -1, 0}, + "GetC": {0, -1, 1, -1}, + "GetNC": {0, 1, 3, -1}, + "GetDC": {1, -1, 2, 0}, + "GetNDC": {1, 2, 4, 0}, +} + +// register go parser +func init() { + AddParser(goParser) +} + +// parse go package +func goParser(dirPath, basePath string, data DomainMap) error { + fileSet := token.NewFileSet() + + conf := packages.Config{ + Mode: packages.NeedName | + packages.NeedFiles | + packages.NeedSyntax | + packages.NeedTypes | + packages.NeedTypesInfo, + Fset: fileSet, + Dir: basePath, + } + + // load package from path + pkgs, err := packages.Load(&packages.Config{ + Mode: conf.Mode, + Fset: fileSet, + Dir: dirPath, + }) + if err != nil || len(pkgs) == 0 { + // not a go package + return nil + } + + // handle each file + for _, node := range pkgs[0].Syntax { + file := GoFile{ + pkgConf: &conf, + filePath: fileSet.Position(node.Package).Filename, + basePath: basePath, + data: data, + fileSet: fileSet, + + importedPackages: map[string]*packages.Package{ + pkgs[0].Name: pkgs[0], + }, + } + + ast.Inspect(node, file.inspectFile) + } + return nil +} + +// GoFile handles the parsing of one go file +type GoFile struct { + filePath string + basePath string + data DomainMap + + fileSet *token.FileSet + pkgConf *packages.Config + + importedPackages map[string]*packages.Package +} + +// getPackage loads module by name +func (g *GoFile) getPackage(name string) (*packages.Package, error) { + pkgs, err := packages.Load(g.pkgConf, name) + if err != nil { + return nil, err + } + if len(pkgs) == 0 { + return nil, nil + } + return pkgs[0], nil +} + +// getType from ident object +func (g *GoFile) getType(ident *ast.Ident) types.Object { + for _, pkg := range g.importedPackages { + if obj, ok := pkg.TypesInfo.Uses[ident]; ok { + return obj + } + } + return nil +} + +func (g *GoFile) inspectFile(n ast.Node) bool { + switch x := n.(type) { + // get names of imported packages + case *ast.ImportSpec: + packageName, _ := strconv.Unquote(x.Path.Value) + + pkg, err := g.getPackage(packageName) + if err != nil { + log.Printf("failed to load package %s: %s", packageName, err) + } else { + if x.Name == nil { + g.importedPackages[pkg.Name] = pkg + } else { + g.importedPackages[x.Name.Name] = pkg + } + } + + // check each function call + case *ast.CallExpr: + g.inspectCallExpr(x) + + default: + print() + } + + return true +} + +// checkType for gotext object +func (g *GoFile) checkType(rawType types.Type) bool { + switch t := rawType.(type) { + case *types.Pointer: + return g.checkType(t.Elem()) + + case *types.Named: + if t.Obj().Pkg().Path() != "github.com/leonelquinteros/gotext" { + return false + } + default: + return false + } + return true +} + +func (g *GoFile) inspectCallExpr(n *ast.CallExpr) { + // must be a selector expression otherwise it is a local function call + expr, ok := n.Fun.(*ast.SelectorExpr) + if !ok { + return + } + + switch e := expr.X.(type) { + // direct call + case *ast.Ident: + // object is a package if the Obj is not set + if e.Obj == nil { + pkg, ok := g.importedPackages[e.Name] + if !ok || pkg.PkgPath != "github.com/leonelquinteros/gotext" { + return + } + + } else { + // validate type of object + if !g.checkType(g.getType(e).Type()) { + return + } + } + + // call to attribute + case *ast.SelectorExpr: + // validate type of object + if !g.checkType(g.getType(e.Sel).Type()) { + return + } + + default: + return + } + + // convert args + args := make([]*ast.BasicLit, len(n.Args)) + for idx, arg := range n.Args { + args[idx], _ = arg.(*ast.BasicLit) + } + + // get position + path, _ := filepath.Rel(g.basePath, g.filePath) + position := fmt.Sprintf("%s:%d", path, g.fileSet.Position(n.Lparen).Line) + + // handle getters + if def, ok := gotextGetter[expr.Sel.String()]; ok { + g.parseGetter(def, args, position) + return + } +} + +func (g *GoFile) parseGetter(def GetterDef, args []*ast.BasicLit, pos string) { + // check if enough arguments are given + if len(args) < def.maxArgIndex() { + return + } + + // get domain + var domain string + if def.Domain == -1 { + domain = "default" // TODO + } else { + domain, _ = strconv.Unquote(args[def.Domain].Value) + } + + trans := Translation{ + MsgId: args[def.Id].Value, + SourceLocations: []string{pos}, + } + if def.Plural > 0 { + trans.MsgIdPlural = args[def.Plural].Value + } + if def.Context > 0 { + trans.Context = args[def.Context].Value + } + + g.data.AddTranslation(domain, &trans) +} diff --git a/cli/xgotext/parser/parser.go b/cli/xgotext/parser/parser.go new file mode 100644 index 0000000..de01f20 --- /dev/null +++ b/cli/xgotext/parser/parser.go @@ -0,0 +1,55 @@ +package parser + +import ( + "os" + "path/filepath" +) + +// ParseDirFunc parses one directory +type ParseDirFunc func(filePath, basePath string, data DomainMap) error + +var knownParser []ParseDirFunc + +// AddParser to the known parser list +func AddParser(parser ParseDirFunc) { + if knownParser == nil { + knownParser = []ParseDirFunc{parser} + } else { + knownParser = append(knownParser, parser) + } +} + +// ParseDir calls all known parser for each directory +func ParseDir(dirPath, basePath string, data DomainMap) error { + dirPath, _ = filepath.Abs(dirPath) + basePath, _ = filepath.Abs(basePath) + + for _, parser := range knownParser { + err := parser(dirPath, basePath, data) + if err != nil { + return err + } + } + return nil +} + +// ParseDirRec calls all known parser for each directory +func ParseDirRec(dirPath string) (DomainMap, error) { + data := make(DomainMap) + dirPath, _ = filepath.Abs(dirPath) + + err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if info.IsDir() { + err := ParseDir(path, dirPath, data) + if err != nil { + return err + } + } + return nil + }) + return data, err +} diff --git a/go.mod b/go.mod index 7a1155b..3768d5b 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,7 @@ module github.com/leonelquinteros/gotext // go: no requirements found in Gopkg.lock + +require golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd + +go 1.13 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a5b842c --- /dev/null +++ b/go.sum @@ -0,0 +1,14 @@ +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd h1:hHkvGJK23seRCflePJnVa9IMv8fsuavSCWKd11kDQFs= +golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=