-
-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #39 from Songmu/fix-version-file-detection
refine version file detection
- Loading branch information
Showing
3 changed files
with
222 additions
and
113 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
package rcpr | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
"regexp" | ||
"sort" | ||
"strings" | ||
"sync" | ||
"syscall" | ||
|
||
"github.com/saracen/walker" | ||
) | ||
|
||
const versionRegBase = `(?i)((?:^|[^-_0-9a-zA-Z])version[^-_0-9a-zA-Z].{0,20})` | ||
|
||
var ( | ||
versionReg = regexp.MustCompile(versionRegBase + `([0-9]+\.[0-9]+\.[0-9]+)`) | ||
// The "testdata" directory is ommited because of the test code for rcpr itself | ||
skipDirs = map[string]bool{ | ||
".git": true, | ||
"testdata": true, | ||
"node_modules": true, | ||
"vendor": true, | ||
"third_party": true, | ||
"extlib": true, | ||
} | ||
skipFiles = map[string]bool{ | ||
"requirements.txt": true, | ||
"cpanfile.snapshot": true, | ||
"package-lock.json": true, | ||
} | ||
) | ||
|
||
func isSkipFile(n string) bool { | ||
n = strings.ToLower(n) | ||
return strings.HasSuffix(n, ".lock") || skipFiles[n] | ||
} | ||
|
||
func detectVersionFile(root string, ver *semv) (string, error) { | ||
verReg, err := regexp.Compile(versionRegBase + regexp.QuoteMeta(ver.Naked())) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
errorCb := func(fpath string, err error) error { | ||
// When running a rcpr binary under the repository root, "text file busy" occurred, | ||
// so I did error handling as this, but it did not solve the problem, and it is a special case, | ||
// so we may not need to do the check in particular. | ||
if os.IsPermission(err) || errors.Is(err, syscall.ETXTBSY) { | ||
return nil | ||
} | ||
return err | ||
} | ||
|
||
fl := &fileList{} | ||
if err := walker.Walk(root, func(fpath string, fi os.FileInfo) error { | ||
if fi.IsDir() { | ||
if skipDirs[fi.Name()] { | ||
return filepath.SkipDir | ||
} | ||
return nil | ||
} | ||
if !fi.Mode().IsRegular() || isSkipFile(fi.Name()) { | ||
return nil | ||
} | ||
joinedPath := filepath.Join(root, fpath) | ||
bs, err := os.ReadFile(joinedPath) | ||
if err != nil { | ||
return errorCb(fpath, err) | ||
} | ||
if verReg.Match(bs) { | ||
fl.append(filepath.ToSlash(joinedPath)) | ||
} | ||
return nil | ||
}, walker.WithErrorCallback(errorCb)); err != nil { | ||
return "", err | ||
} | ||
|
||
// XXX: Whether to adopt a version file when the language is not identifiable? | ||
f, _ := versionFile(fl.list()) | ||
return f, nil | ||
} | ||
|
||
func versionFile(files []string) (string, string) { | ||
if len(files) < 1 { | ||
return "", "" | ||
} | ||
files = fileOrder(files) | ||
var meta string | ||
for _, f := range files { | ||
if strings.HasSuffix(f, ".gemspec") { | ||
return f, "ruby" | ||
} | ||
if strings.HasSuffix(f, ".go") { | ||
return f, "go" | ||
} | ||
if meta != "" { | ||
if strings.HasPrefix(f, "lib/") && strings.HasSuffix(f, ".pm") { | ||
return f, "perl" | ||
} | ||
} | ||
|
||
base := strings.ToLower(filepath.Base(f)) | ||
switch base { | ||
case "setup.py", "setup.cfg": | ||
return f, "python" | ||
case "package.json": | ||
return f, "node" | ||
case "pom.xml": | ||
return f, "java" | ||
case "meta.json": | ||
if meta != "" { | ||
meta = f | ||
} | ||
} | ||
} | ||
|
||
if meta != "" { | ||
return meta, "perl" | ||
} | ||
return files[0], "" | ||
} | ||
|
||
func fileOrder(list []string) []string { | ||
sort.Slice(list, func(i, j int) bool { | ||
x := list[i] | ||
y := list[j] | ||
xdepth := strings.Count(x, "/") | ||
ydepth := strings.Count(y, "/") | ||
if xdepth != ydepth { | ||
return xdepth < ydepth | ||
} | ||
return strings.Compare(x, y) < 0 | ||
}) | ||
return list | ||
} | ||
|
||
type fileList struct { | ||
l []string | ||
mu sync.RWMutex | ||
} | ||
|
||
func (fl *fileList) append(fpath string) { | ||
fl.mu.Lock() | ||
defer fl.mu.Unlock() | ||
fl.l = append(fl.l, fpath) | ||
} | ||
|
||
func (fl *fileList) list() []string { | ||
fl.mu.RLock() | ||
defer fl.mu.RUnlock() | ||
return fl.l | ||
} | ||
|
||
func bumpVersionFile(fpath string, from, to *semv) error { | ||
verReg, err := regexp.Compile(versionRegBase + regexp.QuoteMeta(from.Naked())) | ||
if err != nil { | ||
return err | ||
} | ||
bs, err := os.ReadFile(fpath) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
replaced := false | ||
updated := verReg.ReplaceAllFunc(bs, func(match []byte) []byte { | ||
if replaced { | ||
return match | ||
} | ||
replaced = true | ||
return verReg.ReplaceAll(match, []byte(`${1}`+to.Naked())) | ||
}) | ||
return os.WriteFile(fpath, updated, 0666) | ||
} | ||
|
||
func retrieveVersionFromFile(fpath string, vPrefix bool) (*semv, error) { | ||
bs, err := os.ReadFile(fpath) | ||
if err != nil { | ||
return nil, err | ||
} | ||
m := versionReg.FindSubmatch(bs) | ||
if len(m) < 3 { | ||
return nil, fmt.Errorf("no version detected from file: %s", fpath) | ||
} | ||
ver := string(m[2]) | ||
if vPrefix { | ||
ver = "v" + ver | ||
} | ||
return newSemver(ver) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package rcpr | ||
|
||
import ( | ||
"reflect" | ||
"testing" | ||
) | ||
|
||
func TestFileOrder(t *testing.T) { | ||
input := []string{ | ||
"aaa/ccc", | ||
"aaa.go", | ||
"bbb", | ||
"bb/ccd3", | ||
"l/m/n", | ||
} | ||
|
||
expect := []string{ | ||
"aaa.go", | ||
"bbb", | ||
"aaa/ccc", | ||
"bb/ccd3", | ||
"l/m/n", | ||
} | ||
|
||
got := fileOrder(input) | ||
if !reflect.DeepEqual(got, expect) { | ||
t.Errorf("error: %v", got) | ||
} | ||
} |