Skip to content
This repository has been archived by the owner on Dec 18, 2020. It is now read-only.

Commit

Permalink
Merge pull request #96 from phrase/use-new-globbing
Browse files Browse the repository at this point in the history
use new globbing when pushing locale files
  • Loading branch information
sacry-dyn authored Mar 23, 2017
2 parents 0c19a24 + 6829ea1 commit f883c6c
Show file tree
Hide file tree
Showing 8 changed files with 526 additions and 420 deletions.
29 changes: 29 additions & 0 deletions internal/paths/glob.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,35 @@ func dirGlobOperatorUseValid(pattern string) bool {
return !containsOperator || (operatorIsOwnPathSegment || startsWithOperator)
}

// SplitAtDirGlobOperator splits pattern at the '**' operator, if it's contained, then splits path accordingly,
// dropping the segments that the '**' operator would have matched against. The returned paths will match the returned patterns.
// An error is returned if the '**' operator is incorrectly used in pattern.
func SplitAtDirGlobOperator(path, pattern string) (pathStart, patternStart, pathEnd, patternEnd string, err error) {
if !dirGlobOperatorUseValid(pattern) {
return "", "", "", "", fmt.Errorf("invalid pattern '%s': the ** globbing operator may only be used as path segment on its own, i.e. …/**/… or **/…", pattern)
}

parts := strings.Split(pattern, string(filepath.Separator)+dirGlobOperator+string(filepath.Separator))
patternStart = parts[0]
if len(parts) == 2 {
patternEnd = parts[1]
}

numSegmentsStart := len(Segments(patternStart))
numSegmentsEnd := len(Segments(patternEnd))

segments := Segments(path)

pathStart = filepath.Join(segments[:numSegmentsStart]...)
pathEnd = filepath.Join(segments[len(segments)-numSegmentsEnd:]...)

if strings.HasPrefix(path, "/") && pathStart != "" {
pathStart = "/" + pathStart
}

return
}

// Glob supports * and ** globbing according to https://phraseapp.com/docs/developers/cli/configuration/#globbing
func Glob(pattern string) (matches []string, err error) {
pattern = filepath.Clean(pattern)
Expand Down
50 changes: 50 additions & 0 deletions internal/paths/glob_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,53 @@ func areEqual(s, t []string) bool {

return true
}

func TestSplitAtDirGlobOperator(t *testing.T) {
path := "/foo/bla/bar/baz/asd/a/b/c"

tests := map[string]struct {
pathStart, patternStart, pathEnd, patternEnd string
}{
"/foo/**/asd/a/b/c": {
"/foo",
"/foo",
"asd/a/b/c",
"asd/a/b/c",
},
"/foo/*/bar/**/a/*/c": {
"/foo/bla/bar",
"/foo/*/bar",
"a/b/c",
"a/*/c",
},
"/**/bar/baz/*/a/*/c": {
"",
"",
"bar/baz/asd/a/b/c",
"bar/baz/*/a/*/c",
},
}

for pattern, expected := range tests {
pathStart, patternStart, pathEnd, patternEnd, err := SplitAtDirGlobOperator(path, pattern)
if err != nil {
t.Error(err)
}

if pathStart != expected.pathStart {
t.Errorf("expected path start to be %v, got %v", expected.pathStart, pathStart)
}

if patternStart != expected.patternStart {
t.Errorf("expected pattern start to be %v, got %v", expected.patternStart, patternStart)
}

if pathEnd != expected.pathEnd {
t.Errorf("expected path end to be %v, got %v", expected.pathEnd, pathEnd)
}

if patternEnd != expected.patternEnd {
t.Errorf("expected pattern end to be %v, got %v", expected.patternEnd, patternEnd)
}
}
}
4 changes: 4 additions & 0 deletions internal/paths/paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,9 @@ func IsDir(path string) bool {
}

func Segments(s string) []string {
if s == "" {
return []string{}
}

return strings.FieldsFunc(filepath.Clean(s), func(c rune) bool { return c == filepath.Separator })
}
11 changes: 10 additions & 1 deletion internal/placeholders/placeholders.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,23 @@ func ContainsTagPlaceholder(s string) bool {
}

func ToGlobbingPattern(s string) string {
return anyPlaceholderRegexp.ReplaceAllString(s, "*")
path := anyPlaceholderRegexp.ReplaceAllString(s, "*")
baseName := filepath.Base(s)
extension := filepath.Ext(s)
if baseName == extension {
return strings.Replace(path, baseName, "*"+baseName, 1)
}
return path
}

// Resolve matches s against pattern and maps placeholders in pattern to
// substrings of s.
// Resolve handles '*' wildcards in the pattern, but will return an error
// if the pattern contains '**'.
func Resolve(s, pattern string) (map[string]string, error) {
s = filepath.Clean(s)
pattern = filepath.Clean(pattern)

if strings.Contains(pattern, "**") {
return map[string]string{}, fmt.Errorf("'**' wildcard not allowed in pattern")
}
Expand Down
66 changes: 66 additions & 0 deletions internal/placeholders/placeholders_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,47 @@ func TestResolve(t *testing.T) {
}: {
"tag": "bla",
},
{
"config/locales/en.yml",
"config/locales/<locale_code>.yml",
}: {
"locale_code": "en",
},
{
"abc/defg/en.lproj/Localizable.strings",
"./abc/defg/<locale_code>.lproj/Localizable.strings",
}: {
"locale_code": "en",
},
{
"config/german/de.yml",
"config/<locale_name>/<locale_code>.yml",
}: {
"locale_name": "german",
"locale_code": "de",
},
{
"config/german/de/de.yml",
"config/<locale_name>/<locale_code>/*.yml",
}: {
"locale_name": "german",
"locale_code": "de",
},
{
"abc/en.lproj/MyStoryboard.strings",
"./abc/<locale_code>.lproj/<tag>.strings",
}: {
"locale_code": "en",
"tag": "MyStoryboard",
},
{
"no_tag/abc/play.en",
"*/<tag>/<locale_name>.<locale_code>",
}: {
"locale_name": "play",
"locale_code": "en",
"tag": "abc",
},
}

for input, expected := range tests {
Expand All @@ -35,6 +76,31 @@ func TestResolve(t *testing.T) {
}
}

func TestToGlobbing(t *testing.T) {
tests := []struct {
path string
pattern string
}{
{
path: "abc/*.lproj/*.strings",
pattern: "abc/<locale_code>.lproj/.strings",
}, {
path: "abc/defg/*.lproj/*.strings",
pattern: "abc/defg/<locale_code>.lproj/.strings",
}, {
path: "abc/defg/*.lproj/Localizable.strings",
pattern: "abc/defg/<locale_code>.lproj/Localizable.strings",
},
}
for _, test := range tests {
result := ToGlobbingPattern(test.pattern)

if result != test.path {
t.Errorf("expected result to be %q, but got %q", test.path, result)
}
}
}

func TestResolve_errorPlaceholderReuse(t *testing.T) {
tests := []struct {
path string
Expand Down
84 changes: 26 additions & 58 deletions push.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"github.com/phrase/phraseapp-client/internal/placeholders"
"github.com/phrase/phraseapp-client/internal/print"
"github.com/phrase/phraseapp-client/internal/spinner"
"github.com/phrase/phraseapp-client/internal/stringz"
"github.com/phrase/phraseapp-go/phraseapp"
)

Expand Down Expand Up @@ -160,21 +159,18 @@ func (source *Source) LocaleFiles() (LocaleFiles, error) {
return nil, err
}

patternTokens := paths.Segments(source.File)

var localeFiles LocaleFiles
for _, path := range filePaths {
pathTokens := paths.Segments(path)
localeFile := extractParamsFromPathTokens(patternTokens, pathTokens)
localeFile := new(LocaleFile)
localeFile.fillFromPath(path, source.File)

absolutePath, err := filepath.Abs(path)
localeFile.Path, err = filepath.Abs(path)
if err != nil {
return nil, err
}

localeFile.Path = absolutePath

locale := source.getRemoteLocaleForLocaleFile(localeFile)
// TODO: sinnvoll?
if locale != nil {
localeFile.ExistsRemote = true
localeFile.Code = locale.Code
Expand All @@ -192,13 +188,14 @@ func (source *Source) LocaleFiles() (LocaleFiles, error) {
localeFiles = append(localeFiles, localeFile)
}

if len(localeFiles) <= 0 {
if len(localeFiles) == 0 {
abs, err := filepath.Abs(source.File)
if err != nil {
abs = source.File
}
return nil, fmt.Errorf("Could not find any files on your system that matches: '%s'", abs)
}

return localeFiles, nil
}

Expand Down Expand Up @@ -261,63 +258,34 @@ func (source *Source) getRemoteLocaleForLocaleFile(localeFile *LocaleFile) *phra
}
}

func extractParamsFromPathTokens(patternTokens, pathTokens []string) *LocaleFile {
localeFile := new(LocaleFile)

if Debug {
fmt.Println("pattern:", patternTokens)
fmt.Println("path:", pathTokens)
func (localeFile *LocaleFile) fillFromPath(path, pattern string) {
pathStart, patternStart, pathEnd, patternEnd, err := paths.SplitAtDirGlobOperator(path, pattern)
if err != nil {
print.Error(err)
return
}

for idx, patternToken := range patternTokens {
pathToken := pathTokens[idx]

if patternToken == "*" {
continue
}
if patternToken == "**" {
break
fillFrom := func(path, pattern string) {
params, err := placeholders.Resolve(path, pattern)
if err != nil {
print.Error(err)
return
}
localeFile.extractParamFromPathToken(patternToken, pathToken)
}

if stringz.Contains(patternTokens, "**") {
offset := 1
for idx := len(patternTokens) - 1; idx >= 0; idx-- {
patternToken := patternTokens[idx]
pathToken := pathTokens[len(pathTokens)-offset]
offset += 1

if patternToken == "*" {
continue
} else if patternToken == "**" {
break
for placeholder, value := range params {
switch placeholder {
case "locale_code":
localeFile.Code = value
case "locale_name":
localeFile.Name = value
case "tag":
localeFile.Tag = value
}

localeFile.extractParamFromPathToken(patternToken, pathToken)
}
}

return localeFile
}

func (localeFile *LocaleFile) extractParamFromPathToken(patternToken, pathToken string) {
params, err := placeholders.Resolve(pathToken, patternToken)
if err != nil {
print.Error(err)
return
}

for placeholder, value := range params {
switch placeholder {
case "locale_code":
localeFile.Code = value
case "locale_name":
localeFile.Name = value
case "tag":
localeFile.Tag = value
}
}
fillFrom(pathStart, patternStart)
fillFrom(pathEnd, patternEnd)
}

func (localeFile *LocaleFile) shouldCreateLocale(source *Source) bool {
Expand Down
Loading

0 comments on commit f883c6c

Please sign in to comment.