github.com/xenophex/i18n4go@v0.2.7-0.20160907212557-40256cda157a/cmds/show_missing_strings.go (about) 1 package cmds 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "path/filepath" 8 "strconv" 9 "strings" 10 11 "go/ast" 12 "go/parser" 13 "go/token" 14 15 "github.com/XenoPhex/i18n4go/common" 16 ) 17 18 type ShowMissingStrings struct { 19 options common.Options 20 21 I18nStringInfos []common.I18nStringInfo 22 TranslatedStrings []string 23 I18nStringsFilename string 24 Directory string 25 } 26 27 func NewShowMissingStrings(options common.Options) ShowMissingStrings { 28 return ShowMissingStrings{ 29 options: options, 30 Directory: options.DirnameFlag, 31 I18nStringsFilename: options.I18nStringsFilenameFlag, 32 TranslatedStrings: []string{}, 33 } 34 } 35 36 func (sms *ShowMissingStrings) Options() common.Options { 37 return sms.options 38 } 39 40 func (sms *ShowMissingStrings) Println(a ...interface{}) (int, error) { 41 if sms.options.VerboseFlag { 42 return fmt.Println(a...) 43 } 44 45 return 0, nil 46 } 47 48 func (sms *ShowMissingStrings) Printf(msg string, a ...interface{}) (int, error) { 49 if sms.options.VerboseFlag { 50 return fmt.Printf(msg, a...) 51 } 52 53 return 0, nil 54 } 55 56 func (sms *ShowMissingStrings) Run() error { 57 return sms.showMissingStrings() 58 } 59 60 func (sms *ShowMissingStrings) showMissingStrings() error { 61 62 //Load en-us.all.json 63 64 stringInfos, err := common.LoadI18nStringInfos(sms.I18nStringsFilename) 65 if err != nil { 66 return err 67 } 68 sms.I18nStringInfos = stringInfos 69 70 //Run AST to get list of strings 71 err = sms.parseFiles() 72 if err != nil { 73 return err 74 } 75 76 //Compare list of strings with <lang>.all.json 77 err = sms.showMissingTranslatedStrings() 78 if err != nil { 79 return err 80 } 81 82 //Compare list of translated strings with strings in codebase 83 return sms.showExtraStrings() 84 } 85 86 func (sms *ShowMissingStrings) parseFiles() error { 87 sourceFiles, _ := getFilesAndDir(sms.Directory) 88 for _, sourceFile := range sourceFiles { 89 err := sms.inspectFile(sourceFile) 90 if err != nil { 91 return err 92 } 93 } 94 95 return nil 96 } 97 func (sms *ShowMissingStrings) inspectFile(filename string) error { 98 fset := token.NewFileSet() 99 100 var absFilePath = filename 101 if !filepath.IsAbs(absFilePath) { 102 absFilePath = filepath.Join(os.Getenv("PWD"), absFilePath) 103 } 104 105 fileInfo, err := common.GetAbsFileInfo(absFilePath) 106 if err != nil { 107 sms.Println(err) 108 } 109 110 if strings.HasPrefix(fileInfo.Name(), ".") || !strings.HasSuffix(fileInfo.Name(), ".go") { 111 sms.Println("WARNING ignoring file:", absFilePath) 112 return nil 113 } 114 115 astFile, err := parser.ParseFile(fset, absFilePath, nil, parser.ParseComments|parser.AllErrors) 116 if err != nil { 117 sms.Println(err) 118 return err 119 } 120 121 return sms.extractString(astFile, fset, filename) 122 } 123 124 func (sms *ShowMissingStrings) extractString(f *ast.File, fset *token.FileSet, filename string) error { 125 ast.Inspect(f, func(n ast.Node) bool { 126 switch x := n.(type) { 127 case *ast.CallExpr: 128 switch x.Fun.(type) { 129 case *ast.Ident: 130 funName := x.Fun.(*ast.Ident).Name 131 132 if funName == "T" || funName == "t" { 133 if stringArg, ok := x.Args[0].(*ast.BasicLit); ok { 134 translatedString, err := strconv.Unquote(stringArg.Value) 135 if err != nil { 136 panic(err.Error()) 137 } 138 139 sms.Println("Adding to translated strings:", translatedString) 140 sms.TranslatedStrings = append(sms.TranslatedStrings, filename+": "+translatedString) 141 } 142 } 143 default: 144 //Skip! 145 } 146 } 147 return true 148 }) 149 150 return nil 151 } 152 153 func (sms *ShowMissingStrings) showMissingTranslatedStrings() error { 154 missingStrings := false 155 for _, codeString := range sms.TranslatedStrings { 156 if !sms.stringInStringInfos(codeString, sms.I18nStringInfos) { 157 fmt.Println("Missing:", codeString) 158 missingStrings = true 159 } 160 } 161 162 if missingStrings { 163 return errors.New("Missing Strings!") 164 } 165 166 return nil 167 } 168 169 func splitFilePathAndString(str string) (string, string) { 170 splitFileStr := strings.SplitAfterN(str, ": ", 2) 171 return splitFileStr[0], splitFileStr[1] 172 } 173 174 func (sms *ShowMissingStrings) stringInStringInfos(str string, list []common.I18nStringInfo) bool { 175 _, translatedStr := splitFilePathAndString(str) 176 for _, stringInfo := range list { 177 if translatedStr == stringInfo.ID { 178 sms.Println("Found", stringInfo.ID, "UNDER", str) 179 return true 180 } 181 } 182 183 return false 184 } 185 186 // Compares translated strings with strings in codebase. 187 func (sms *ShowMissingStrings) showExtraStrings() error { 188 additionalStrings := false 189 for _, stringInfo := range sms.I18nStringInfos { 190 if !stringInTranslatedStrings(stringInfo.ID, sms.TranslatedStrings) { 191 fmt.Println("Additional:", stringInfo.ID) 192 additionalStrings = true 193 } 194 } 195 if additionalStrings { 196 return errors.New("Additional Strings!") 197 } 198 return nil 199 } 200 201 func stringInTranslatedStrings(stringInfoID string, list []string) bool { 202 for _, fileAndStr := range list { 203 _, translatedStr := splitFilePathAndString(fileAndStr) 204 if translatedStr == stringInfoID { 205 return true 206 } 207 } 208 209 return false 210 }