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  }