Commit 48b3ab43 authored by Kirill Smelkov's avatar Kirill Smelkov

Move some string-related utilities to https://lab.nexedi.com/kirr/go123/xstrings/

	xstrings.SplitLines
	xstrings.Split2
	xstrings.HeadTail

Other string-related routines stay in git-backup for now as I don't
feel they are general enough or interface chosen is really ok.
parent 0be1f647
......@@ -79,6 +79,7 @@ import (
"lab.nexedi.com/kirr/go123/mem"
"lab.nexedi.com/kirr/go123/myname"
"lab.nexedi.com/kirr/go123/xerr"
"lab.nexedi.com/kirr/go123/xstrings"
git "github.com/libgit2/git2go"
)
......@@ -262,7 +263,7 @@ func obj_recreate_from_commit(g *git.Repository, commit_sha1 Sha1) Sha1 {
xraise(">1 parents")
}
obj_type, obj_raw, err := headtail(commit.Message(), "\n")
obj_type, obj_raw, err := xstrings.HeadTail(commit.Message(), "\n")
if err != nil {
xraise("invalid encoded format")
}
......@@ -334,7 +335,7 @@ func cmd_pull(gb *git.Repository, argv []string) {
pullspecv := []PullSpec{}
for _, arg := range argv {
dir, prefix, err := split2(arg, ":")
dir, prefix, err := xstrings.Split2(arg, ":")
if err != nil {
fmt.Fprintf(os.Stderr, "E: invalid pullspec %q\n", arg)
cmd_pull_usage()
......@@ -464,7 +465,7 @@ func cmd_pull_(gb *git.Repository, pullspecv []PullSpec) {
backup_refsv := []string{} // backup.refs content
backup_refs_parents := Sha1Set{} // sha1 for commit parents, obtained from refs
noncommit_seen := map[Sha1]Sha1{} // {} sha1 -> sha1_ (there are many duplicate tags)
for _, __ := range splitlines(backup_refs_dump, "\n") {
for _, __ := range xstrings.SplitLines(backup_refs_dump, "\n") {
sha1, type_, ref := Sha1{}, "", ""
_, err := fmt.Sscanf(__, "%s %s %s\n", &sha1, &type_, &ref)
if err != nil {
......@@ -597,7 +598,7 @@ func cmd_restore(gb *git.Repository, argv []string) {
restorespecv := []RestoreSpec{}
for _, arg := range argv[1:] {
prefix, dir, err := split2(arg, ":")
prefix, dir, err := xstrings.Split2(arg, ":")
if err != nil {
fmt.Fprintf(os.Stderr, "E: invalid restorespec %q\n", arg)
cmd_restore_usage()
......@@ -710,7 +711,7 @@ func cmd_restore_(gb *git.Repository, HEAD_ string, restorespecv []RestoreSpec)
// read backup refs index
repotab := map[string]*BackupRepo{} // repo.path -> repo
backup_refs := xgit("cat-file", "blob", fmt.Sprintf("%s:backup.refs", HEAD))
for _, refentry := range splitlines(backup_refs, "\n") {
for _, refentry := range xstrings.SplitLines(backup_refs, "\n") {
// sha1 prefix+refname (sha1_)
badentry := func() { exc.Raisef("E: invalid backup.refs entry: %q", refentry) }
refentryv := strings.Fields(refentry)
......@@ -779,7 +780,7 @@ func cmd_restore_(gb *git.Repository, HEAD_ string, restorespecv []RestoreSpec)
// files
lstree := xgit("ls-tree", "--full-tree", "-r", "-z", "--", HEAD, prefix, RunWith{raw: true})
repos_seen := StrSet{} // dirs of *.git seen while restoring files
for _, __ := range splitlines(lstree, "\x00") {
for _, __ := range xstrings.SplitLines(lstree, "\x00") {
mode, type_, sha1, filename, err := parse_lstree_entry(__)
// NOTE
// - `ls-tree -r` shows only leaf objects
......
......@@ -25,6 +25,7 @@ import (
"lab.nexedi.com/kirr/go123/exc"
"lab.nexedi.com/kirr/go123/myname"
"lab.nexedi.com/kirr/go123/xruntime"
"lab.nexedi.com/kirr/go123/xstrings"
git "github.com/libgit2/git2go"
)
......@@ -197,7 +198,7 @@ func TestPullRestore(t *testing.T) {
gitObjectsRe := regexp.MustCompile(`\.git/objects/`)
for _, diffline := range strings.Split(diff, "\n") {
// :srcmode dstmode srcsha1 dstsha1 status\tpath
_, path, err := headtail(diffline, "\t")
_, path, err := xstrings.HeadTail(diffline, "\t")
if err != nil {
t.Fatalf("restorecheck: cannot parse diff line %q", diffline)
}
......
......@@ -23,6 +23,7 @@ import (
"lab.nexedi.com/kirr/go123/exc"
"lab.nexedi.com/kirr/go123/mem"
"lab.nexedi.com/kirr/go123/xstrings"
git "github.com/libgit2/git2go"
)
......@@ -129,7 +130,7 @@ func tag_parse(tag_raw string) (*Tag, error) {
// parse lstree entry
func parse_lstree_entry(lsentry string) (mode uint32, type_ string, sha1 Sha1, filename string, err error) {
// <mode> SP <type> SP <object> TAB <file> # NOTE file can contain spaces
__, filename, err1 := headtail(lsentry, "\t")
__, filename, err1 := xstrings.HeadTail(lsentry, "\t")
_, err2 := fmt.Sscanf(__, "%o %s %s\n", &mode, &type_, &sha1)
if err1 != nil || err2 != nil {
......
......@@ -25,35 +25,6 @@ import (
"lab.nexedi.com/kirr/go123/mem"
)
// split string into lines. The last line, if it is empty, is omitted from the result
// (rationale is: string.Split("hello\nworld\n", "\n") -> ["hello", "world", ""])
func splitlines(s, sep string) []string {
sv := strings.Split(s, sep)
l := len(sv)
if l > 0 && sv[l-1] == "" {
sv = sv[:l-1]
}
return sv
}
// split string by sep and expect exactly 2 parts
func split2(s, sep string) (s1, s2 string, err error) {
parts := strings.Split(s, sep)
if len(parts) != 2 {
return "", "", fmt.Errorf("split2: %q has %v parts (expected 2, sep: %q)", s, len(parts), sep)
}
return parts[0], parts[1], nil
}
// (head+sep+tail) -> head, tail
func headtail(s, sep string) (head, tail string, err error) {
parts := strings.SplitN(s, sep, 2)
if len(parts) != 2 {
return "", "", fmt.Errorf("headtail: %q has no %q", s, sep)
}
return parts[0], parts[1], nil
}
// strip_prefix("/a/b", "/a/b/c/d/e") -> "c/d/e" (without leading /)
// path must start with prefix
func strip_prefix(prefix, path string) string {
......
......@@ -13,67 +13,10 @@
package main
import (
"reflect"
"strings"
"testing"
)
func TestSplitLines(t *testing.T) {
var tests = []struct { input, sep string; output []string } {
{"", "\n", []string{}},
{"hello", "\n", []string{"hello"}},
{"hello\n", "\n", []string{"hello"}},
{"hello\nworld", "\n", []string{"hello", "world"}},
{"hello\nworld\n", "\n", []string{"hello", "world"}},
{"hello\x00world\x00", "\n", []string{"hello\x00world\x00"}},
{"hello\x00world\x00", "\x00", []string{"hello", "world"}},
}
for _, tt := range tests {
sv := splitlines(tt.input, tt.sep)
if !reflect.DeepEqual(sv, tt.output) {
t.Errorf("splitlines(%q, %q) -> %q ; want %q", tt.input, tt.sep, sv, tt.output)
}
}
}
func TestSplit2(t *testing.T) {
var tests = []struct { input, s1, s2 string; ok bool } {
{"", "", "", false},
{" ", "", "", true},
{"hello", "", "", false},
{"hello world", "hello", "world", true},
{"hello world 1", "", "", false},
}
for _, tt := range tests {
s1, s2, err := split2(tt.input, " ")
ok := err == nil
if s1 != tt.s1 || s2 != tt.s2 || ok != tt.ok {
t.Errorf("split2(%q) -> %q %q %v ; want %q %q %v", tt.input, s1, s2, ok, tt.s1, tt.s2, tt.ok)
}
}
}
func TestHeadtail(t *testing.T) {
var tests = []struct { input, head, tail string; ok bool } {
{"", "", "", false},
{" ", "", "", true},
{" ", "", " ", true},
{"hello world", "hello", "world", true},
{"hello world 1", "hello", "world 1", true},
{"hello world 2", "hello", " world 2", true},
}
for _, tt := range tests {
head, tail, err := headtail(tt.input, " ")
ok := err == nil
if head != tt.head || tail != tt.tail || ok != tt.ok {
t.Errorf("headtail(%q) -> %q %q %v ; want %q %q %v", tt.input, head, tail, ok, tt.head, tt.tail, tt.ok)
}
}
}
func TestPathEscapeUnescape(t *testing.T) {
type TestEntry struct { path string; escapedv []string }
te := func(path string, escaped ...string) TestEntry {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment