commit a9524a41770330a82fdf6f0442299bd316ede686 Author: Benjamin Gaudé Date: Fri Dec 6 16:26:20 2019 +0100 Premier commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5032f0d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.tmp \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..b176efc --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Namecheck + +Exercice de formation Go réalisé du 2/12/19 au 4/12/19 \ No newline at end of file diff --git a/cmd/cli/main.go b/cmd/cli/main.go new file mode 100644 index 0000000..236be02 --- /dev/null +++ b/cmd/cli/main.go @@ -0,0 +1,40 @@ +package main + +import ( + "fmt" + "log" + "os" + "sync" + "time" + + "github.com/foxdeveloper/namecheck" + _ "github.com/foxdeveloper/namecheck/github" + _ "github.com/foxdeveloper/namecheck/twitter" +) + +func main() { + var wg sync.WaitGroup + ch := make(chan string) + start := time.Now() + argsUsername := os.Args[1] + + //go namecheck.Run(namecheck.SocialNetworks(), argsUsername, ch) + for _, sn := range namecheck.SocialNetworks() { + wg.Add(1) + go func(sn namecheck.SocialNetwork) { + namecheck.Namecheck(sn, argsUsername, &wg, ch) + }(sn) + } + + go func() { + wg.Wait() + close(ch) + }() + + for v := range ch { + fmt.Printf("%s", v) + } + + elapsed := time.Since(start) + log.Printf("Took %s", elapsed) +} diff --git a/cmd/server/main.go b/cmd/server/main.go new file mode 100644 index 0000000..8a306ad --- /dev/null +++ b/cmd/server/main.go @@ -0,0 +1,43 @@ +package main + +import ( + "fmt" + "log" + "net/http" + "sync" + "time" + + "github.com/foxdeveloper/namecheck" + _ "github.com/foxdeveloper/namecheck/github" + _ "github.com/foxdeveloper/namecheck/twitter" +) + +func handler(w http.ResponseWriter, r *http.Request) { + var wg sync.WaitGroup + ch := make(chan string) + for _, sn := range namecheck.SocialNetworks() { + wg.Add(1) + go func(sn namecheck.SocialNetwork) { + namecheck.Namecheck(sn, r.URL.Path[1:], &wg, ch) + }(sn) + } + + go func() { + wg.Wait() + close(ch) + }() + + for v := range ch { + fmt.Fprintf(w, "%s", v) + } +} + +func main() { + start := time.Now() + + http.HandleFunc("/", handler) + log.Fatal(http.ListenAndServe(":8080", nil)) + + elapsed := time.Since(start) + log.Printf("Took %s", elapsed) +} diff --git a/cmd/test/main.go b/cmd/test/main.go new file mode 100644 index 0000000..3fc4d53 --- /dev/null +++ b/cmd/test/main.go @@ -0,0 +1,39 @@ +package main + +import ( + "fmt" + "sync" +) + +func prepareSouffle() { + fmt.Println("Je prépare un souffle") +} + +func preparePatatoes() { + fmt.Println("Je prépare des patates") +} + +func printTenIntsConcurrently(ch chan int) { + var wg sync.WaitGroup + for i := 0; i < 10; i++ { + wg.Add(1) + go printInt(i, &wg, &ch) + } + wg.Wait() + close(ch) +} + +func printInt(i int, wg *sync.WaitGroup, ch *chan int) { + defer wg.Done() + *ch <- i +} + +func main() { + ints := make(chan int) + + go printTenIntsConcurrently(ints) + + for i := range ints { + fmt.Printf("%d\n", i) + } +} diff --git a/github/github.go b/github/github.go new file mode 100644 index 0000000..c91ae87 --- /dev/null +++ b/github/github.go @@ -0,0 +1,71 @@ +package github + +import ( + "net/http" + "regexp" + + "github.com/foxdeveloper/namecheck" + "github.com/foxdeveloper/namecheck/util" +) + +const ( + EndpointAPI string = "https://github.com/" + AvailableHTTPCode int = http.StatusNotFound + UsernameShort int = 1 + UsernameLong int = 15 + UsernameLegalPattern string = "^[A-Za-z0-9_]+$" + UsernameIllegalPattern string = "github" +) + +var ( + legalRegexp = regexp.MustCompile(UsernameLegalPattern) + illegalRegexp = regexp.MustCompile(UsernameIllegalPattern) +) + +type Github struct{} + +func init() { + namecheck.AddSn(&Github{}) +} + +func (t *Github) String() string { + return "Github" +} + +// IsValid check the validity of the username +func (t *Github) IsValid(username string) bool { + return isLongEnough(username) && + isShortEnough(username) && + containsNoIllegalPattern(username) && + containsOnlyLegalChars(username) +} + +// IsAvailable check username availability +func (t *Github) IsAvailable(username string) (bool, error) { + res, err := http.Get(EndpointAPI + username) + if err != nil { + myErr := &namecheck.ErrUnknownAvailability{Username: username, Cause: err} + return false, myErr + } + + // Prevent memory leaks + defer res.Body.Close() + + return res.StatusCode == AvailableHTTPCode, nil +} + +func isLongEnough(username string) bool { + return util.Counter(username) >= UsernameShort +} + +func isShortEnough(username string) bool { + return util.Counter(username) <= UsernameLong +} + +func containsOnlyLegalChars(username string) bool { + return legalRegexp.MatchString(util.ToLower(username)) +} + +func containsNoIllegalPattern(username string) bool { + return len(illegalRegexp.FindStringIndex(util.ToLower(username))) == 0 +} diff --git a/github/github_test.go b/github/github_test.go new file mode 100644 index 0000000..35a67d0 --- /dev/null +++ b/github/github_test.go @@ -0,0 +1,68 @@ +package github_test + +import ( + "testing" +) + +var ( + gh github.Github = github.Github{} +) + +func TestName(t *testing.T) { + want := "Twitter" + got := tw.String() + + if got != want { + t.Errorf("twitter.Name() = %s; want %s", got, want) + } + +} +func TestUsernameTooShort(t *testing.T) { + username := "" + want := false + got := tw.IsValid(username) + + if got != want { + t.Errorf("twitter.IsValid(%s) = %t; want %t", username, got, want) + } +} +func TestUsernameTooLong(t *testing.T) { + username := "azetrgdkalakdzjazdzaidadhazdazidazdazmdazldkazda" + want := false + got := tw.IsValid(username) + + if got != want { + t.Errorf("twitter.IsValid(%s) = %t; want %t", username, got, want) + } +} +func TestUsernameContainsIllegalPattern(t *testing.T) { + username := "psetwiTterdo" + want := false + got := tw.IsValid(username) + + if got != want { + t.Errorf("twitter.IsValid(%s) = %t; want %t", username, got, want) + } +} +func TestUsernameContainsNotOnlyLegalPattern(t *testing.T) { + username := "pseudo-" + want := false + got := tw.IsValid(username) + + if got != want { + t.Errorf("twitter.IsValid(%s) = %t; want %t", username, got, want) + } +} +func TestUsernameAvailablePattern(t *testing.T) { + username := "jubobs" + want := false + got, err := tw.IsAvailable(username) + + if err != nil { + t.Errorf("%v", err) + } + + if got != want { + t.Errorf("twitter.IsValid(%s) = %t; want %t", username, got, want) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..28fa45f --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/foxdeveloper/namecheck + +go 1.13 diff --git a/namecheck.go b/namecheck.go new file mode 100644 index 0000000..057eae8 --- /dev/null +++ b/namecheck.go @@ -0,0 +1,81 @@ +package namecheck + +import ( + "fmt" + "sync" +) + +var ( + usernames []string = []string{"foxdeveloper666", "jubobs"} + sn []SocialNetwork +) + +type MyError interface { + error + Unwrap() error +} + +type ErrUnknownAvailability struct { + Username string + Cause error +} + +func (e *ErrUnknownAvailability) Unwrap() error { + return e.Cause +} + +func (e *ErrUnknownAvailability) Error() string { + return fmt.Sprintf("Unknown availability error for %s", e.Username) +} + +type Validator interface { + IsValid(username string) bool +} + +type Provider interface { + IsAvailable(username string) (bool, error) +} + +type SocialNetwork interface { + fmt.Stringer + Validator + Provider +} + +func AddSn(social SocialNetwork) { + sn = append(sn, social) +} + +func SocialNetworks() []SocialNetwork { + return sn +} + +func Namecheck(sn SocialNetwork, username string, wg *sync.WaitGroup, ch chan string) { + defer wg.Done() + if ok := sn.IsValid(username); !ok { + ch <- fmt.Sprintf("%s is not valid!", username) + } + + ok, err := sn.IsAvailable(username) + + if err != nil { + err, ok := err.(MyError) + if ok { + cause := err.Unwrap() + ch <- fmt.Sprintf("%v\n", cause) + } + ch <- fmt.Sprintf("%s\n", err.Error()) + } + + ch <- fmt.Sprintf("On %s \"%v\" is Available : %v\n", sn.String(), username, ok) +} + +func Run(sns []SocialNetwork, username string, ch chan string) { + var wg sync.WaitGroup + for _, sn := range sns { + wg.Add(1) + go Namecheck(sn, username, &wg, ch) + } + wg.Wait() + close(ch) +} diff --git a/twitter/twitter.go b/twitter/twitter.go new file mode 100644 index 0000000..00c8361 --- /dev/null +++ b/twitter/twitter.go @@ -0,0 +1,71 @@ +package twitter + +import ( + "net/http" + "regexp" + + "github.com/foxdeveloper/namecheck" + "github.com/foxdeveloper/namecheck/util" +) + +const ( + EndpointAPI string = "https://twitter.com/" + AvailableHTTPCode int = http.StatusNotFound + UsernameShort int = 1 + UsernameLong int = 15 + UsernameLegalPattern string = "^[A-Za-z0-9_]+$" + UsernameIllegalPattern string = "twitter" +) + +var ( + legalRegexp = regexp.MustCompile(UsernameLegalPattern) + illegalRegexp = regexp.MustCompile(UsernameIllegalPattern) +) + +type Twitter struct{} + +func init() { + namecheck.AddSn(&Twitter{}) +} + +func (t *Twitter) String() string { + return "Twitter" +} + +// IsValid check the validity of the username +func (t *Twitter) IsValid(username string) bool { + return isLongEnough(username) && + isShortEnough(username) && + containsNoIllegalPattern(username) && + containsOnlyLegalChars(username) +} + +// IsAvailable check username availability +func (t *Twitter) IsAvailable(username string) (bool, error) { + res, err := http.Get(EndpointAPI + username) + if err != nil { + myErr := &namecheck.ErrUnknownAvailability{Username: username, Cause: err} + return false, myErr + } + + // Prevent memory leaks + defer res.Body.Close() + + return res.StatusCode == AvailableHTTPCode, nil +} + +func isLongEnough(username string) bool { + return util.Counter(username) >= UsernameShort +} + +func isShortEnough(username string) bool { + return util.Counter(username) <= UsernameLong +} + +func containsOnlyLegalChars(username string) bool { + return legalRegexp.MatchString(util.ToLower(username)) +} + +func containsNoIllegalPattern(username string) bool { + return len(illegalRegexp.FindStringIndex(util.ToLower(username))) == 0 +} diff --git a/twitter/twitter_test.go b/twitter/twitter_test.go new file mode 100644 index 0000000..ba89d7a --- /dev/null +++ b/twitter/twitter_test.go @@ -0,0 +1,69 @@ +package twitter_test + +import ( + "github.com/foxdeveloper/namecheck/twitter" + "testing" +) + +var ( + tw twitter.Twitter = twitter.Twitter{} +) + +func TestName(t *testing.T) { + want := "Twitter" + got := tw.String() + + if got != want { + t.Errorf("twitter.Name() = %s; want %s", got, want) + } + +} +func TestUsernameTooShort(t *testing.T) { + username := "" + want := false + got := tw.IsValid(username) + + if got != want { + t.Errorf("twitter.IsValid(%s) = %t; want %t", username, got, want) + } +} +func TestUsernameTooLong(t *testing.T) { + username := "azetrgdkalakdzjazdzaidadhazdazidazdazmdazldkazda" + want := false + got := tw.IsValid(username) + + if got != want { + t.Errorf("twitter.IsValid(%s) = %t; want %t", username, got, want) + } +} +func TestUsernameContainsIllegalPattern(t *testing.T) { + username := "psetwiTterdo" + want := false + got := tw.IsValid(username) + + if got != want { + t.Errorf("twitter.IsValid(%s) = %t; want %t", username, got, want) + } +} +func TestUsernameContainsNotOnlyLegalPattern(t *testing.T) { + username := "pseudo-" + want := false + got := tw.IsValid(username) + + if got != want { + t.Errorf("twitter.IsValid(%s) = %t; want %t", username, got, want) + } +} +func TestUsernameAvailablePattern(t *testing.T) { + username := "jubobs" + want := false + got, err := tw.IsAvailable(username) + + if err != nil { + t.Errorf("%v", err) + } + + if got != want { + t.Errorf("twitter.IsValid(%s) = %t; want %t", username, got, want) + } +} diff --git a/util/common.go b/util/common.go new file mode 100644 index 0000000..382c15f --- /dev/null +++ b/util/common.go @@ -0,0 +1,16 @@ +package util + +import ( + "strings" + "unicode/utf8" +) + +// Counter symbols +func Counter(s string) int { + return utf8.RuneCountInString(s) +} + +// ToLower string +func ToLower(s string) string { + return strings.ToLower(s) +} diff --git a/util/common_test.go b/util/common_test.go new file mode 100644 index 0000000..300831e --- /dev/null +++ b/util/common_test.go @@ -0,0 +1,27 @@ +package util_test + +import ( + "testing" + + "github.com/foxdeveloper/namecheck/util" +) + +func TestToLower(t *testing.T) { + test := "UPPERCASE" + want := "uppercase" + got := util.ToLower(test) + + if got != want { + t.Errorf("util.ToLower(%s) = %s; want %s", test, got, want) + } +} + +func TestCounter(t *testing.T) { + test := "传/傳test" + want := 7 + got := util.Counter(test) + + if got != want { + t.Errorf("util.Counter(%s) = %d; want %d", test, got, want) + } +}