From c6e087bf6a09936a769e5b252830a65bf6d129ca Mon Sep 17 00:00:00 2001 From: Alex Rodin Date: Wed, 18 Aug 2021 09:29:06 -0400 Subject: [PATCH 1/5] feat: Added support for autolinks (#2044). --- github/github-accessors.go | 24 +++++ github/github-accessors_test.go | 30 ++++++ github/repos_autolinks.go | 98 ++++++++++++++++++++ github/repos_autolinks_test.go | 156 ++++++++++++++++++++++++++++++++ 4 files changed, 308 insertions(+) create mode 100644 github/repos_autolinks.go create mode 100644 github/repos_autolinks_test.go diff --git a/github/github-accessors.go b/github/github-accessors.go index bd94ba944c2..1df095d3037 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -1188,6 +1188,30 @@ func (a *AuthorizationUpdateRequest) GetNoteURL() string { return *a.NoteURL } +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (a *Autolink) GetID() int64 { + if a == nil || a.ID == nil { + return 0 + } + return *a.ID +} + +// GetKeyPrefix returns the KeyPrefix field if it's non-nil, zero value otherwise. +func (a *AutolinkOptions) GetKeyPrefix() string { + if a == nil || a.KeyPrefix == nil { + return "" + } + return *a.KeyPrefix +} + +// GetURLTemplate returns the URLTemplate field if it's non-nil, zero value otherwise. +func (a *AutolinkOptions) GetURLTemplate() string { + if a == nil || a.URLTemplate == nil { + return "" + } + return *a.URLTemplate +} + // GetAppID returns the AppID field if it's non-nil, zero value otherwise. func (a *AutoTriggerCheck) GetAppID() int64 { if a == nil || a.AppID == nil { diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index 4264ce8b9ad..c57d30036a4 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -1417,6 +1417,36 @@ func TestAuthorizationUpdateRequest_GetNoteURL(tt *testing.T) { a.GetNoteURL() } +func TestAutolink_GetID(tt *testing.T) { + var zeroValue int64 + a := &Autolink{ID: &zeroValue} + a.GetID() + a = &Autolink{} + a.GetID() + a = nil + a.GetID() +} + +func TestAutolinkOptions_GetKeyPrefix(tt *testing.T) { + var zeroValue string + a := &AutolinkOptions{KeyPrefix: &zeroValue} + a.GetKeyPrefix() + a = &AutolinkOptions{} + a.GetKeyPrefix() + a = nil + a.GetKeyPrefix() +} + +func TestAutolinkOptions_GetURLTemplate(tt *testing.T) { + var zeroValue string + a := &AutolinkOptions{URLTemplate: &zeroValue} + a.GetURLTemplate() + a = &AutolinkOptions{} + a.GetURLTemplate() + a = nil + a.GetURLTemplate() +} + func TestAutoTriggerCheck_GetAppID(tt *testing.T) { var zeroValue int64 a := &AutoTriggerCheck{AppID: &zeroValue} diff --git a/github/repos_autolinks.go b/github/repos_autolinks.go new file mode 100644 index 00000000000..9671259b3ce --- /dev/null +++ b/github/repos_autolinks.go @@ -0,0 +1,98 @@ +// Copyright 2021 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// AutolinkOptions specifies parameters for RepositoriesService.AddAutolink method +// Also this struct is embedded into an Autolink struct which contains one additional ID field +type AutolinkOptions struct { + KeyPrefix *string `json:"key_prefix,omitempty"` + URLTemplate *string `json:"url_template,omitempty"` +} + +// Autolink embeds an AutolinkOptions struct and is used by +// RepositoriesService.ListAutolinks, RepositoriesService.AddAutolink and RepositoriesService.GetAutolink methods +type Autolink struct { + ID *int64 `json:"id,omitempty"` + *AutolinkOptions +} + +// ListAutolinks returns a list of autolinks configured for the given repository. +// Information about autolinks are only available to repository administrators. +// GitHub API docs: https://docs.github.com/en/rest/reference/repos#list-all-autolinks-of-a-repository +func (s *RepositoriesService) ListAutolinks(ctx context.Context, owner, repo string, opts *ListOptions) ([]*Autolink, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/autolinks", owner, repo) + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var autolinks []*Autolink + resp, err := s.client.Do(ctx, req, &autolinks) + if err != nil { + return nil, resp, err + } + + return autolinks, resp, nil +} + +// AddAutolink creates an autolink reference for a repository. +// Users with admin access to the repository can create an autolink. +// GitHub API docs: https://docs.github.com/en/rest/reference/repos#create-an-autolink-reference-for-a-repository +func (s *RepositoriesService) AddAutolink(ctx context.Context, owner, repo string, opts *AutolinkOptions) (*Autolink, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/autolinks", owner, repo) + req, err := s.client.NewRequest("POST", u, opts) + if err != nil { + return nil, nil, err + } + al := new(Autolink) + resp, err := s.client.Do(ctx, req, al) + if err != nil { + return nil, resp, err + } + return al, resp, nil +} + +// GetAutolink returns a single autolink reference by ID that was configured for the given repository. +// Information about autolinks are only available to repository administrators. +// GitHub API docs: https://docs.github.com/en/rest/reference/repos#get-an-autolink-reference-of-a-repository +func (s *RepositoriesService) GetAutolink(ctx context.Context, owner, repo string, id int) (*Autolink, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/autolinks/%d", owner, repo, id) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var autolink *Autolink + resp, err := s.client.Do(ctx, req, &autolink) + if err != nil { + return nil, resp, err + } + + return autolink, resp, nil +} + +// DeleteAutolink deletes a single autolink reference by ID that was configured for the given repository. +// Information about autolinks are only available to repository administrators. +// GitHub API docs: https://docs.github.com/en/rest/reference/repos#delete-an-autolink-reference-from-a-repository +func (s *RepositoriesService) DeleteAutolink(ctx context.Context, owner, repo string, id int) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/autolinks/%d", owner, repo, id) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(ctx, req, nil) +} diff --git a/github/repos_autolinks_test.go b/github/repos_autolinks_test.go new file mode 100644 index 00000000000..11be7a23ee9 --- /dev/null +++ b/github/repos_autolinks_test.go @@ -0,0 +1,156 @@ +// Copyright 2021 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestRepositoriesService_ListAutolinks(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/autolinks", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprintf(w, `[{"id":1, "key_prefix": "TICKET-", "url_template": "https://example.com/TICKET?query="}, {"id":2, "key_prefix": "STORY-", "url_template": "https://example.com/STORY?query="}]`) + }) + + opt := &ListOptions{ + Page: 2, + } + ctx := context.Background() + autolinks, _, err := client.Repositories.ListAutolinks(ctx, "o", "r", opt) + if err != nil { + t.Errorf("Repositories.ListAutolinks returned error: %v", err) + } + + want := []*Autolink{ + {ID: Int64(1), AutolinkOptions: &AutolinkOptions{KeyPrefix: String("TICKET-"), URLTemplate: String("https://example.com/TICKET?query=")}}, + {ID: Int64(2), AutolinkOptions: &AutolinkOptions{KeyPrefix: String("STORY-"), URLTemplate: String("https://example.com/STORY?query=")}}, + } + + if !cmp.Equal(autolinks, want) { + t.Errorf("Repositories.ListAutolinks returned %+v, want %+v", autolinks, want) + } + + const methodName = "ListAutolinks" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Repositories.ListAutolinks(ctx, "\n", "\n", opt) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Repositories.ListAutolinks(ctx, "o", "r", opt) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestRepositoriesService_AddAutolink(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + opt := &AutolinkOptions{KeyPrefix: String("TICKET-"), URLTemplate: String("https://example.com/TICKET?query=")} + mux.HandleFunc("/repos/o/r/autolinks", func(w http.ResponseWriter, r *http.Request) { + v := new(AutolinkOptions) + json.NewDecoder(r.Body).Decode(v) + testMethod(t, r, "POST") + if !cmp.Equal(v, opt) { + t.Errorf("Request body = %+v, want %+v", v, opt) + } + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"key_prefix": "TICKET-","url_template": "https://example.com/TICKET?query="}`)) + }) + ctx := context.Background() + autolink, _, err := client.Repositories.AddAutolink(ctx, "o", "r", opt) + if err != nil { + t.Errorf("Repositories.AddAutolink returned error: %v", err) + } + want := &Autolink{ + AutolinkOptions: &AutolinkOptions{ + KeyPrefix: String("TICKET-"), + URLTemplate: String("https://example.com/TICKET?query="), + }, + } + + if !cmp.Equal(autolink, want) { + t.Errorf("AddAutolink returned %+v, want %+v", autolink, want) + } + + const methodName = "AddAutolink" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Repositories.AddAutolink(ctx, "\n", "\n", opt) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Repositories.AddAutolink(ctx, "o", "r", opt) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestRepositoriesService_GetAutolink(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/autolinks/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprintf(w, `{"id":1, "key_prefix": "TICKET-", "url_template": "https://example.com/TICKET?query="}`) + }) + + ctx := context.Background() + autolink, _, err := client.Repositories.GetAutolink(ctx, "o", "r", 1) + if err != nil { + t.Errorf("Repositories.GetAutolink returned error: %v", err) + } + + want := &Autolink{ID: Int64(1), AutolinkOptions: &AutolinkOptions{KeyPrefix: String("TICKET-"), URLTemplate: String("https://example.com/TICKET?query=")}} + if !cmp.Equal(autolink, want) { + t.Errorf("Repositories.GetAutolink returned %+v, want %+v", autolink, want) + } + + const methodName = "GetAutolink" + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Repositories.GetAutolink(ctx, "o", "r", 2) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestRepositoriesService_DeleteAutolink(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/autolinks/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + + ctx := context.Background() + _, err := client.Repositories.DeleteAutolink(ctx, "o", "r", 1) + if err != nil { + t.Errorf("Repositories.DeleteAutolink returned error: %v", err) + } + + const methodName = "DeleteAutolink" + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + return client.Repositories.DeleteAutolink(ctx, "o", "r", 2) + }) +} From be0037000cba1c6e81e6ba682fd8dc4b71a4a7e7 Mon Sep 17 00:00:00 2001 From: Alex Rodin Date: Wed, 18 Aug 2021 10:48:24 -0400 Subject: [PATCH 2/5] Made the required changes (#2044). --- github/repos_autolinks.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/github/repos_autolinks.go b/github/repos_autolinks.go index 9671259b3ce..7f7d6c82e95 100644 --- a/github/repos_autolinks.go +++ b/github/repos_autolinks.go @@ -10,7 +10,7 @@ import ( "fmt" ) -// AutolinkOptions specifies parameters for RepositoriesService.AddAutolink method +// AutolinkOptions specifies parameters for RepositoriesService.AddAutolink method. // Also this struct is embedded into an Autolink struct which contains one additional ID field type AutolinkOptions struct { KeyPrefix *string `json:"key_prefix,omitempty"` @@ -26,6 +26,7 @@ type Autolink struct { // ListAutolinks returns a list of autolinks configured for the given repository. // Information about autolinks are only available to repository administrators. +// // GitHub API docs: https://docs.github.com/en/rest/reference/repos#list-all-autolinks-of-a-repository func (s *RepositoriesService) ListAutolinks(ctx context.Context, owner, repo string, opts *ListOptions) ([]*Autolink, *Response, error) { u := fmt.Sprintf("repos/%v/%v/autolinks", owner, repo) @@ -50,6 +51,7 @@ func (s *RepositoriesService) ListAutolinks(ctx context.Context, owner, repo str // AddAutolink creates an autolink reference for a repository. // Users with admin access to the repository can create an autolink. +// // GitHub API docs: https://docs.github.com/en/rest/reference/repos#create-an-autolink-reference-for-a-repository func (s *RepositoriesService) AddAutolink(ctx context.Context, owner, repo string, opts *AutolinkOptions) (*Autolink, *Response, error) { u := fmt.Sprintf("repos/%v/%v/autolinks", owner, repo) @@ -57,6 +59,7 @@ func (s *RepositoriesService) AddAutolink(ctx context.Context, owner, repo strin if err != nil { return nil, nil, err } + al := new(Autolink) resp, err := s.client.Do(ctx, req, al) if err != nil { @@ -67,9 +70,10 @@ func (s *RepositoriesService) AddAutolink(ctx context.Context, owner, repo strin // GetAutolink returns a single autolink reference by ID that was configured for the given repository. // Information about autolinks are only available to repository administrators. +// // GitHub API docs: https://docs.github.com/en/rest/reference/repos#get-an-autolink-reference-of-a-repository -func (s *RepositoriesService) GetAutolink(ctx context.Context, owner, repo string, id int) (*Autolink, *Response, error) { - u := fmt.Sprintf("repos/%v/%v/autolinks/%d", owner, repo, id) +func (s *RepositoriesService) GetAutolink(ctx context.Context, owner, repo string, id int64) (*Autolink, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/autolinks/%v", owner, repo, id) req, err := s.client.NewRequest("GET", u, nil) if err != nil { @@ -87,9 +91,10 @@ func (s *RepositoriesService) GetAutolink(ctx context.Context, owner, repo strin // DeleteAutolink deletes a single autolink reference by ID that was configured for the given repository. // Information about autolinks are only available to repository administrators. +// // GitHub API docs: https://docs.github.com/en/rest/reference/repos#delete-an-autolink-reference-from-a-repository -func (s *RepositoriesService) DeleteAutolink(ctx context.Context, owner, repo string, id int) (*Response, error) { - u := fmt.Sprintf("repos/%v/%v/autolinks/%d", owner, repo, id) +func (s *RepositoriesService) DeleteAutolink(ctx context.Context, owner, repo string, id int64) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/autolinks/%v", owner, repo, id) req, err := s.client.NewRequest("DELETE", u, nil) if err != nil { return nil, err From 015f14c5324a4d7334c0585d45f9f763398179df Mon Sep 17 00:00:00 2001 From: Alex Rodin Date: Wed, 18 Aug 2021 11:03:53 -0400 Subject: [PATCH 3/5] Removed an embedded AutolinkOptions struct from Autolink struct (#2044) --- github/github-accessors.go | 16 ++++++++++++++++ github/github-accessors_test.go | 20 ++++++++++++++++++++ github/repos_autolinks.go | 9 +++++---- github/repos_autolinks_test.go | 12 +++++------- 4 files changed, 46 insertions(+), 11 deletions(-) diff --git a/github/github-accessors.go b/github/github-accessors.go index 1df095d3037..780f03ca084 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -1196,6 +1196,22 @@ func (a *Autolink) GetID() int64 { return *a.ID } +// GetKeyPrefix returns the KeyPrefix field if it's non-nil, zero value otherwise. +func (a *Autolink) GetKeyPrefix() string { + if a == nil || a.KeyPrefix == nil { + return "" + } + return *a.KeyPrefix +} + +// GetURLTemplate returns the URLTemplate field if it's non-nil, zero value otherwise. +func (a *Autolink) GetURLTemplate() string { + if a == nil || a.URLTemplate == nil { + return "" + } + return *a.URLTemplate +} + // GetKeyPrefix returns the KeyPrefix field if it's non-nil, zero value otherwise. func (a *AutolinkOptions) GetKeyPrefix() string { if a == nil || a.KeyPrefix == nil { diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index c57d30036a4..29b41b889a1 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -1427,6 +1427,26 @@ func TestAutolink_GetID(tt *testing.T) { a.GetID() } +func TestAutolink_GetKeyPrefix(tt *testing.T) { + var zeroValue string + a := &Autolink{KeyPrefix: &zeroValue} + a.GetKeyPrefix() + a = &Autolink{} + a.GetKeyPrefix() + a = nil + a.GetKeyPrefix() +} + +func TestAutolink_GetURLTemplate(tt *testing.T) { + var zeroValue string + a := &Autolink{URLTemplate: &zeroValue} + a.GetURLTemplate() + a = &Autolink{} + a.GetURLTemplate() + a = nil + a.GetURLTemplate() +} + func TestAutolinkOptions_GetKeyPrefix(tt *testing.T) { var zeroValue string a := &AutolinkOptions{KeyPrefix: &zeroValue} diff --git a/github/repos_autolinks.go b/github/repos_autolinks.go index 7f7d6c82e95..84debdc1360 100644 --- a/github/repos_autolinks.go +++ b/github/repos_autolinks.go @@ -17,11 +17,12 @@ type AutolinkOptions struct { URLTemplate *string `json:"url_template,omitempty"` } -// Autolink embeds an AutolinkOptions struct and is used by -// RepositoriesService.ListAutolinks, RepositoriesService.AddAutolink and RepositoriesService.GetAutolink methods +// Autolink represents an autolink reference of a repository and is used by +// RepositoriesService.ListAutolinks, RepositoriesService.AddAutolink and RepositoriesService.GetAutolink methods. type Autolink struct { - ID *int64 `json:"id,omitempty"` - *AutolinkOptions + ID *int64 `json:"id,omitempty"` + KeyPrefix *string `json:"key_prefix,omitempty"` + URLTemplate *string `json:"url_template,omitempty"` } // ListAutolinks returns a list of autolinks configured for the given repository. diff --git a/github/repos_autolinks_test.go b/github/repos_autolinks_test.go index 11be7a23ee9..b6121d2c7bd 100644 --- a/github/repos_autolinks_test.go +++ b/github/repos_autolinks_test.go @@ -35,8 +35,8 @@ func TestRepositoriesService_ListAutolinks(t *testing.T) { } want := []*Autolink{ - {ID: Int64(1), AutolinkOptions: &AutolinkOptions{KeyPrefix: String("TICKET-"), URLTemplate: String("https://example.com/TICKET?query=")}}, - {ID: Int64(2), AutolinkOptions: &AutolinkOptions{KeyPrefix: String("STORY-"), URLTemplate: String("https://example.com/STORY?query=")}}, + {ID: Int64(1), KeyPrefix: String("TICKET-"), URLTemplate: String("https://example.com/TICKET?query=")}, + {ID: Int64(2), KeyPrefix: String("STORY-"), URLTemplate: String("https://example.com/STORY?query=")}, } if !cmp.Equal(autolinks, want) { @@ -79,10 +79,8 @@ func TestRepositoriesService_AddAutolink(t *testing.T) { t.Errorf("Repositories.AddAutolink returned error: %v", err) } want := &Autolink{ - AutolinkOptions: &AutolinkOptions{ - KeyPrefix: String("TICKET-"), - URLTemplate: String("https://example.com/TICKET?query="), - }, + KeyPrefix: String("TICKET-"), + URLTemplate: String("https://example.com/TICKET?query="), } if !cmp.Equal(autolink, want) { @@ -119,7 +117,7 @@ func TestRepositoriesService_GetAutolink(t *testing.T) { t.Errorf("Repositories.GetAutolink returned error: %v", err) } - want := &Autolink{ID: Int64(1), AutolinkOptions: &AutolinkOptions{KeyPrefix: String("TICKET-"), URLTemplate: String("https://example.com/TICKET?query=")}} + want := &Autolink{ID: Int64(1), KeyPrefix: String("TICKET-"), URLTemplate: String("https://example.com/TICKET?query=")} if !cmp.Equal(autolink, want) { t.Errorf("Repositories.GetAutolink returned %+v, want %+v", autolink, want) } From 6f10561285732f21d115da1c7914b79f4663ec73 Mon Sep 17 00:00:00 2001 From: Alex Rodin Date: Wed, 18 Aug 2021 11:18:24 -0400 Subject: [PATCH 4/5] Deleted a comment about embedded AutolinkOptions struct (#2044) --- github/repos_autolinks.go | 1 - 1 file changed, 1 deletion(-) diff --git a/github/repos_autolinks.go b/github/repos_autolinks.go index 84debdc1360..47397a47ed0 100644 --- a/github/repos_autolinks.go +++ b/github/repos_autolinks.go @@ -11,7 +11,6 @@ import ( ) // AutolinkOptions specifies parameters for RepositoriesService.AddAutolink method. -// Also this struct is embedded into an Autolink struct which contains one additional ID field type AutolinkOptions struct { KeyPrefix *string `json:"key_prefix,omitempty"` URLTemplate *string `json:"url_template,omitempty"` From 77595276ce13788381f54a43935d32d3c13671a0 Mon Sep 17 00:00:00 2001 From: Alex Rodin Date: Wed, 18 Aug 2021 11:24:48 -0400 Subject: [PATCH 5/5] Updated a comment about Autolink struct (#2044) --- github/repos_autolinks.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/github/repos_autolinks.go b/github/repos_autolinks.go index 47397a47ed0..b6404783eb7 100644 --- a/github/repos_autolinks.go +++ b/github/repos_autolinks.go @@ -16,8 +16,7 @@ type AutolinkOptions struct { URLTemplate *string `json:"url_template,omitempty"` } -// Autolink represents an autolink reference of a repository and is used by -// RepositoriesService.ListAutolinks, RepositoriesService.AddAutolink and RepositoriesService.GetAutolink methods. +// Autolink represents autolinks to external resources like JIRA issues and Zendesk tickets. type Autolink struct { ID *int64 `json:"id,omitempty"` KeyPrefix *string `json:"key_prefix,omitempty"`