mirror of
https://github.com/golang/go
synced 2024-11-23 07:50:05 -07:00
bytes, strings: add TrimPrefix and TrimSuffix
Everybody either gets confused and thinks this is TrimLeft/TrimRight or does this by hand which gets repetitive looking. R=rsc, kevlar CC=golang-dev https://golang.org/cl/7239044
This commit is contained in:
parent
fe14ee52cc
commit
e515d80d5d
@ -778,8 +778,7 @@ func (w *Walker) walkConst(vs *ast.ValueSpec) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(litType, constDepPrefix) {
|
if dep := strings.TrimPrefix(litType, constDepPrefix); dep != litType {
|
||||||
dep := litType[len(constDepPrefix):]
|
|
||||||
w.constDep[ident.Name] = dep
|
w.constDep[ident.Name] = dep
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -1542,8 +1542,8 @@ func godefsFields(fld []*ast.Field) {
|
|||||||
npad := 0
|
npad := 0
|
||||||
for _, f := range fld {
|
for _, f := range fld {
|
||||||
for _, n := range f.Names {
|
for _, n := range f.Names {
|
||||||
if strings.HasPrefix(n.Name, prefix) && n.Name != prefix {
|
if n.Name != prefix {
|
||||||
n.Name = n.Name[len(prefix):]
|
n.Name = strings.TrimPrefix(n.Name, prefix)
|
||||||
}
|
}
|
||||||
if n.Name == "_" {
|
if n.Name == "_" {
|
||||||
// Use exported name instead.
|
// Use exported name instead.
|
||||||
|
@ -180,7 +180,7 @@ func (p *Package) cdefs(f *File, srcfile string) string {
|
|||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
line = strings.TrimSpace(line)
|
line = strings.TrimSpace(line)
|
||||||
if strings.HasPrefix(line, "type ") && strings.HasSuffix(line, " struct {") {
|
if strings.HasPrefix(line, "type ") && strings.HasSuffix(line, " struct {") {
|
||||||
s := line[len("type ") : len(line)-len(" struct {")]
|
s := strings.TrimSuffix(strings.TrimPrefix(line, "type "), " struct {")
|
||||||
printf("typedef struct %s %s;\n", s, s)
|
printf("typedef struct %s %s;\n", s, s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -395,9 +395,7 @@ func typecheck1(cfg *TypeConfig, f interface{}, typeof map[interface{}]string, a
|
|||||||
// Field or method.
|
// Field or method.
|
||||||
name := n.Sel.Name
|
name := n.Sel.Name
|
||||||
if t := typeof[n.X]; t != "" {
|
if t := typeof[n.X]; t != "" {
|
||||||
if strings.HasPrefix(t, "*") {
|
t = strings.TrimPrefix(t, "*") // implicit *
|
||||||
t = t[1:] // implicit *
|
|
||||||
}
|
|
||||||
if typ := cfg.Type[t]; typ != nil {
|
if typ := cfg.Type[t]; typ != nil {
|
||||||
if t := typ.dot(cfg, name); t != "" {
|
if t := typ.dot(cfg, name); t != "" {
|
||||||
typeof[n] = t
|
typeof[n] = t
|
||||||
|
@ -195,9 +195,7 @@ func testFlag(args []string, i int) (f *testFlagSpec, value string, extra bool)
|
|||||||
}
|
}
|
||||||
name := arg[1:]
|
name := arg[1:]
|
||||||
// If there's already "test.", drop it for now.
|
// If there's already "test.", drop it for now.
|
||||||
if strings.HasPrefix(name, "test.") {
|
name = strings.TrimPrefix(name, "test.")
|
||||||
name = name[5:]
|
|
||||||
}
|
|
||||||
equals := strings.Index(name, "=")
|
equals := strings.Index(name, "=")
|
||||||
if equals >= 0 {
|
if equals >= 0 {
|
||||||
value = name[equals+1:]
|
value = name[equals+1:]
|
||||||
|
@ -229,9 +229,7 @@ func (dir *Directory) lookupLocal(name string) *Directory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func splitPath(p string) []string {
|
func splitPath(p string) []string {
|
||||||
if strings.HasPrefix(p, "/") {
|
p = strings.TrimPrefix(p, "/")
|
||||||
p = p[1:]
|
|
||||||
}
|
|
||||||
if p == "" {
|
if p == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -310,14 +308,9 @@ func (root *Directory) listing(skipRoot bool) *DirList {
|
|||||||
// the path is relative to root.Path - remove the root.Path
|
// the path is relative to root.Path - remove the root.Path
|
||||||
// prefix (the prefix should always be present but avoid
|
// prefix (the prefix should always be present but avoid
|
||||||
// crashes and check)
|
// crashes and check)
|
||||||
path := d.Path
|
path := strings.TrimPrefix(d.Path, root.Path)
|
||||||
if strings.HasPrefix(d.Path, root.Path) {
|
|
||||||
path = d.Path[len(root.Path):]
|
|
||||||
}
|
|
||||||
// remove leading separator if any - path must be relative
|
// remove leading separator if any - path must be relative
|
||||||
if len(path) > 0 && path[0] == '/' {
|
path = strings.TrimPrefix(path, "/")
|
||||||
path = path[1:]
|
|
||||||
}
|
|
||||||
p.Path = path
|
p.Path = path
|
||||||
p.Name = d.Name
|
p.Name = d.Name
|
||||||
p.HasPkg = d.HasPkg
|
p.HasPkg = d.HasPkg
|
||||||
|
@ -459,9 +459,7 @@ func (ns nameSpace) ReadDir(path string) ([]os.FileInfo, error) {
|
|||||||
if hasPathPrefix(old, path) && old != path {
|
if hasPathPrefix(old, path) && old != path {
|
||||||
// Find next element after path in old.
|
// Find next element after path in old.
|
||||||
elem := old[len(path):]
|
elem := old[len(path):]
|
||||||
if strings.HasPrefix(elem, "/") {
|
elem = strings.TrimPrefix(elem, "/")
|
||||||
elem = elem[1:]
|
|
||||||
}
|
|
||||||
if i := strings.Index(elem, "/"); i >= 0 {
|
if i := strings.Index(elem, "/"); i >= 0 {
|
||||||
elem = elem[:i]
|
elem = elem[:i]
|
||||||
}
|
}
|
||||||
|
@ -419,9 +419,7 @@ func pkgLinkFunc(path string) string {
|
|||||||
relpath := path[1:]
|
relpath := path[1:]
|
||||||
// because of the irregular mapping under goroot
|
// because of the irregular mapping under goroot
|
||||||
// we need to correct certain relative paths
|
// we need to correct certain relative paths
|
||||||
if strings.HasPrefix(relpath, "src/pkg/") {
|
relpath = strings.TrimPrefix(relpath, "src/pkg/")
|
||||||
relpath = relpath[len("src/pkg/"):]
|
|
||||||
}
|
|
||||||
return pkgHandler.pattern[1:] + relpath // remove trailing '/' for relative URL
|
return pkgHandler.pattern[1:] + relpath // remove trailing '/' for relative URL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -347,7 +347,7 @@ func main() {
|
|||||||
fs.Bind(target, OS(path), "/", bindReplace)
|
fs.Bind(target, OS(path), "/", bindReplace)
|
||||||
abspath = target
|
abspath = target
|
||||||
} else if strings.HasPrefix(path, cmdPrefix) {
|
} else if strings.HasPrefix(path, cmdPrefix) {
|
||||||
path = path[len(cmdPrefix):]
|
path = strings.TrimPrefix(path, cmdPrefix)
|
||||||
forceCmd = true
|
forceCmd = true
|
||||||
} else if bp, _ := build.Import(path, "", build.FindOnly); bp.Dir != "" && bp.ImportPath != "" {
|
} else if bp, _ := build.Import(path, "", build.FindOnly); bp.Dir != "" && bp.ImportPath != "" {
|
||||||
fs.Bind(target, OS(bp.Dir), "/", bindReplace)
|
fs.Bind(target, OS(bp.Dir), "/", bindReplace)
|
||||||
|
@ -90,9 +90,7 @@ func (f *File) checkCanonicalMethod(id *ast.Ident, t *ast.FuncType) {
|
|||||||
fmt.Fprintf(&f.b, "<%s>", err)
|
fmt.Fprintf(&f.b, "<%s>", err)
|
||||||
}
|
}
|
||||||
actual := f.b.String()
|
actual := f.b.String()
|
||||||
if strings.HasPrefix(actual, "func(") {
|
actual = strings.TrimPrefix(actual, "func(")
|
||||||
actual = actual[4:]
|
|
||||||
}
|
|
||||||
actual = id.Name + actual
|
actual = id.Name + actual
|
||||||
|
|
||||||
f.Warnf(id.Pos(), "method %s should have signature %s", actual, expectFmt)
|
f.Warnf(id.Pos(), "method %s should have signature %s", actual, expectFmt)
|
||||||
|
@ -515,6 +515,24 @@ func TrimFunc(s []byte, f func(r rune) bool) []byte {
|
|||||||
return TrimRightFunc(TrimLeftFunc(s, f), f)
|
return TrimRightFunc(TrimLeftFunc(s, f), f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TrimPrefix returns s without the provided leading prefix string.
|
||||||
|
// If s doesn't start with prefix, s is returned unchanged.
|
||||||
|
func TrimPrefix(s, prefix []byte) []byte {
|
||||||
|
if HasPrefix(s, prefix) {
|
||||||
|
return s[len(prefix):]
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrimSuffix returns s without the provided trailing suffix string.
|
||||||
|
// If s doesn't end with suffix, s is returned unchanged.
|
||||||
|
func TrimSuffix(s, suffix []byte) []byte {
|
||||||
|
if HasSuffix(s, suffix) {
|
||||||
|
return s[:len(s)-len(suffix)]
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
// IndexFunc interprets s as a sequence of UTF-8-encoded Unicode code points.
|
// IndexFunc interprets s as a sequence of UTF-8-encoded Unicode code points.
|
||||||
// It returns the byte index in s of the first Unicode
|
// It returns the byte index in s of the first Unicode
|
||||||
// code point satisfying f(c), or -1 if none do.
|
// code point satisfying f(c), or -1 if none do.
|
||||||
|
@ -794,8 +794,8 @@ func TestRunes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type TrimTest struct {
|
type TrimTest struct {
|
||||||
f string
|
f string
|
||||||
in, cutset, out string
|
in, arg, out string
|
||||||
}
|
}
|
||||||
|
|
||||||
var trimTests = []TrimTest{
|
var trimTests = []TrimTest{
|
||||||
@ -820,12 +820,17 @@ var trimTests = []TrimTest{
|
|||||||
{"TrimRight", "", "123", ""},
|
{"TrimRight", "", "123", ""},
|
||||||
{"TrimRight", "", "", ""},
|
{"TrimRight", "", "", ""},
|
||||||
{"TrimRight", "☺\xc0", "☺", "☺\xc0"},
|
{"TrimRight", "☺\xc0", "☺", "☺\xc0"},
|
||||||
|
{"TrimPrefix", "aabb", "a", "abb"},
|
||||||
|
{"TrimPrefix", "aabb", "b", "aabb"},
|
||||||
|
{"TrimSuffix", "aabb", "a", "aabb"},
|
||||||
|
{"TrimSuffix", "aabb", "b", "aab"},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTrim(t *testing.T) {
|
func TestTrim(t *testing.T) {
|
||||||
for _, tc := range trimTests {
|
for _, tc := range trimTests {
|
||||||
name := tc.f
|
name := tc.f
|
||||||
var f func([]byte, string) []byte
|
var f func([]byte, string) []byte
|
||||||
|
var fb func([]byte, []byte) []byte
|
||||||
switch name {
|
switch name {
|
||||||
case "Trim":
|
case "Trim":
|
||||||
f = Trim
|
f = Trim
|
||||||
@ -833,12 +838,21 @@ func TestTrim(t *testing.T) {
|
|||||||
f = TrimLeft
|
f = TrimLeft
|
||||||
case "TrimRight":
|
case "TrimRight":
|
||||||
f = TrimRight
|
f = TrimRight
|
||||||
|
case "TrimPrefix":
|
||||||
|
fb = TrimPrefix
|
||||||
|
case "TrimSuffix":
|
||||||
|
fb = TrimSuffix
|
||||||
default:
|
default:
|
||||||
t.Errorf("Undefined trim function %s", name)
|
t.Errorf("Undefined trim function %s", name)
|
||||||
}
|
}
|
||||||
actual := string(f([]byte(tc.in), tc.cutset))
|
var actual string
|
||||||
|
if f != nil {
|
||||||
|
actual = string(f([]byte(tc.in), tc.arg))
|
||||||
|
} else {
|
||||||
|
actual = string(fb([]byte(tc.in), []byte(tc.arg)))
|
||||||
|
}
|
||||||
if actual != tc.out {
|
if actual != tc.out {
|
||||||
t.Errorf("%s(%q, %q) = %q; want %q", name, tc.in, tc.cutset, actual, tc.out)
|
t.Errorf("%s(%q, %q) = %q; want %q", name, tc.in, tc.arg, actual, tc.out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,3 +66,20 @@ func ExampleCompare_search() {
|
|||||||
// Found it!
|
// Found it!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ExampleTrimSuffix() {
|
||||||
|
var b = []byte("Hello, goodbye, etc!")
|
||||||
|
b = bytes.TrimSuffix(b, []byte("goodbye, etc!"))
|
||||||
|
b = bytes.TrimSuffix(b, []byte("gopher"))
|
||||||
|
b = append(b, bytes.TrimSuffix([]byte("world!"), []byte("x!"))...)
|
||||||
|
os.Stdout.Write(b)
|
||||||
|
// Output: Hello, world!
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleTrimPrefix() {
|
||||||
|
var b = []byte("Goodbye,, world!")
|
||||||
|
b = bytes.TrimPrefix(b, []byte("Goodbye,"))
|
||||||
|
b = bytes.TrimPrefix(b, []byte("See ya,"))
|
||||||
|
fmt.Printf("Hello%s", b)
|
||||||
|
// Output: Hello, world!
|
||||||
|
}
|
||||||
|
@ -42,10 +42,7 @@ func readParseTest(r *bufio.Reader) (text, want, context string, err error) {
|
|||||||
}
|
}
|
||||||
b = append(b, line...)
|
b = append(b, line...)
|
||||||
}
|
}
|
||||||
text = string(b)
|
text = strings.TrimSuffix(string(b), "\n")
|
||||||
if strings.HasSuffix(text, "\n") {
|
|
||||||
text = text[:len(text)-1]
|
|
||||||
}
|
|
||||||
b = b[:0]
|
b = b[:0]
|
||||||
|
|
||||||
// Skip the error list.
|
// Skip the error list.
|
||||||
|
@ -551,9 +551,7 @@ func stripCommonPrefix(lines []string) {
|
|||||||
}
|
}
|
||||||
// Shorten the computed common prefix by the length of
|
// Shorten the computed common prefix by the length of
|
||||||
// suffix, if it is found as suffix of the prefix.
|
// suffix, if it is found as suffix of the prefix.
|
||||||
if strings.HasSuffix(prefix, string(suffix)) {
|
prefix = strings.TrimSuffix(prefix, string(suffix))
|
||||||
prefix = prefix[0 : len(prefix)-len(suffix)]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,10 +44,7 @@ func FindPkg(path, srcDir string) (filename, id string) {
|
|||||||
if bp.PkgObj == "" {
|
if bp.PkgObj == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
noext = bp.PkgObj
|
noext = strings.TrimSuffix(bp.PkgObj, ".a")
|
||||||
if strings.HasSuffix(noext, ".a") {
|
|
||||||
noext = noext[:len(noext)-len(".a")]
|
|
||||||
}
|
|
||||||
|
|
||||||
case build.IsLocalImport(path):
|
case build.IsLocalImport(path):
|
||||||
// "./x" -> "/this/directory/x.ext", "/this/directory/x"
|
// "./x" -> "/this/directory/x.ext", "/this/directory/x"
|
||||||
|
@ -172,7 +172,7 @@ func (name handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
// listing the available profiles.
|
// listing the available profiles.
|
||||||
func Index(w http.ResponseWriter, r *http.Request) {
|
func Index(w http.ResponseWriter, r *http.Request) {
|
||||||
if strings.HasPrefix(r.URL.Path, "/debug/pprof/") {
|
if strings.HasPrefix(r.URL.Path, "/debug/pprof/") {
|
||||||
name := r.URL.Path[len("/debug/pprof/"):]
|
name := strings.TrimPrefix(r.URL.Path, "/debug/pprof/")
|
||||||
if name != "" {
|
if name != "" {
|
||||||
handler(name).ServeHTTP(w, r)
|
handler(name).ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
|
@ -198,9 +198,7 @@ func (r *Response) Write(w io.Writer) error {
|
|||||||
}
|
}
|
||||||
protoMajor, protoMinor := strconv.Itoa(r.ProtoMajor), strconv.Itoa(r.ProtoMinor)
|
protoMajor, protoMinor := strconv.Itoa(r.ProtoMajor), strconv.Itoa(r.ProtoMinor)
|
||||||
statusCode := strconv.Itoa(r.StatusCode) + " "
|
statusCode := strconv.Itoa(r.StatusCode) + " "
|
||||||
if strings.HasPrefix(text, statusCode) {
|
text = strings.TrimPrefix(text, statusCode)
|
||||||
text = text[len(statusCode):]
|
|
||||||
}
|
|
||||||
io.WriteString(w, "HTTP/"+protoMajor+"."+protoMinor+" "+statusCode+text+"\r\n")
|
io.WriteString(w, "HTTP/"+protoMajor+"."+protoMinor+" "+statusCode+text+"\r\n")
|
||||||
|
|
||||||
// Process Body,ContentLength,Close,Trailer
|
// Process Body,ContentLength,Close,Trailer
|
||||||
|
@ -179,3 +179,19 @@ func ExampleToLower() {
|
|||||||
fmt.Println(strings.ToLower("Gopher"))
|
fmt.Println(strings.ToLower("Gopher"))
|
||||||
// Output: gopher
|
// Output: gopher
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ExampleTrimSuffix() {
|
||||||
|
var s = "Hello, goodbye, etc!"
|
||||||
|
s = strings.TrimSuffix(s, "goodbye, etc!")
|
||||||
|
s = strings.TrimSuffix(s, "planet")
|
||||||
|
fmt.Print(s, "world!")
|
||||||
|
// Output: Hello, world!
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleTrimPrefix() {
|
||||||
|
var s = "Goodbye,, world!"
|
||||||
|
s = strings.TrimPrefix(s, "Goodbye,")
|
||||||
|
s = strings.TrimPrefix(s, "Howdy,")
|
||||||
|
fmt.Print("Hello" + s)
|
||||||
|
// Output: Hello, world!
|
||||||
|
}
|
||||||
|
@ -558,6 +558,24 @@ func TrimSpace(s string) string {
|
|||||||
return TrimFunc(s, unicode.IsSpace)
|
return TrimFunc(s, unicode.IsSpace)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TrimPrefix returns s without the provided leading prefix string.
|
||||||
|
// If s doesn't start with prefix, s is returned unchanged.
|
||||||
|
func TrimPrefix(s, prefix string) string {
|
||||||
|
if HasPrefix(s, prefix) {
|
||||||
|
return s[len(prefix):]
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrimSuffix returns s without the provided trailing suffix string.
|
||||||
|
// If s doesn't end with suffix, s is returned unchanged.
|
||||||
|
func TrimSuffix(s, suffix string) string {
|
||||||
|
if HasSuffix(s, suffix) {
|
||||||
|
return s[:len(s)-len(suffix)]
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
// Replace returns a copy of the string s with the first n
|
// Replace returns a copy of the string s with the first n
|
||||||
// non-overlapping instances of old replaced by new.
|
// non-overlapping instances of old replaced by new.
|
||||||
// If n < 0, there is no limit on the number of replacements.
|
// If n < 0, there is no limit on the number of replacements.
|
||||||
|
@ -496,8 +496,8 @@ func TestSpecialCase(t *testing.T) {
|
|||||||
func TestTrimSpace(t *testing.T) { runStringTests(t, TrimSpace, "TrimSpace", trimSpaceTests) }
|
func TestTrimSpace(t *testing.T) { runStringTests(t, TrimSpace, "TrimSpace", trimSpaceTests) }
|
||||||
|
|
||||||
var trimTests = []struct {
|
var trimTests = []struct {
|
||||||
f string
|
f string
|
||||||
in, cutset, out string
|
in, arg, out string
|
||||||
}{
|
}{
|
||||||
{"Trim", "abba", "a", "bb"},
|
{"Trim", "abba", "a", "bb"},
|
||||||
{"Trim", "abba", "ab", ""},
|
{"Trim", "abba", "ab", ""},
|
||||||
@ -520,6 +520,10 @@ var trimTests = []struct {
|
|||||||
{"TrimRight", "", "123", ""},
|
{"TrimRight", "", "123", ""},
|
||||||
{"TrimRight", "", "", ""},
|
{"TrimRight", "", "", ""},
|
||||||
{"TrimRight", "☺\xc0", "☺", "☺\xc0"},
|
{"TrimRight", "☺\xc0", "☺", "☺\xc0"},
|
||||||
|
{"TrimPrefix", "aabb", "a", "abb"},
|
||||||
|
{"TrimPrefix", "aabb", "b", "aabb"},
|
||||||
|
{"TrimSuffix", "aabb", "a", "aabb"},
|
||||||
|
{"TrimSuffix", "aabb", "b", "aab"},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTrim(t *testing.T) {
|
func TestTrim(t *testing.T) {
|
||||||
@ -533,12 +537,16 @@ func TestTrim(t *testing.T) {
|
|||||||
f = TrimLeft
|
f = TrimLeft
|
||||||
case "TrimRight":
|
case "TrimRight":
|
||||||
f = TrimRight
|
f = TrimRight
|
||||||
|
case "TrimPrefix":
|
||||||
|
f = TrimPrefix
|
||||||
|
case "TrimSuffix":
|
||||||
|
f = TrimSuffix
|
||||||
default:
|
default:
|
||||||
t.Errorf("Undefined trim function %s", name)
|
t.Errorf("Undefined trim function %s", name)
|
||||||
}
|
}
|
||||||
actual := f(tc.in, tc.cutset)
|
actual := f(tc.in, tc.arg)
|
||||||
if actual != tc.out {
|
if actual != tc.out {
|
||||||
t.Errorf("%s(%q, %q) = %q; want %q", name, tc.in, tc.cutset, actual, tc.out)
|
t.Errorf("%s(%q, %q) = %q; want %q", name, tc.in, tc.arg, actual, tc.out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user