diff --git a/README.md b/README.md index b47ce2a..c09a48c 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ The included tools are: - [X] Get a random string of length n - [ ] Post JSON to a remote service - [X] Create a directory, including all parent directories, if it does not already exist -- [ ] Create a URL safe slug from a string +- [X] Create a URL safe slug from a string ## Installation diff --git a/tools.go b/tools.go index 72b3faa..32adbf8 100644 --- a/tools.go +++ b/tools.go @@ -8,9 +8,11 @@ import ( "net/http" "os" "path/filepath" + "regexp" "strings" ) +// randomStringSource defines the character set used for generating random strings. const randomStringSource = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVXWYZ0123456789_+" // Tools is the type used to instantiate this module. Any variable of this type will have access to all the methods with the receiver *Tools. @@ -54,6 +56,8 @@ func (t *Tools) UploadOneFile(r *http.Request, uploadDir string, rename ...bool) return files[0], nil } +// UploadFiles uploads multiple files from the provided HTTP request, storing them in the specified directory. +// If the optional rename argument is true or not provided, the files will be renamed. func (t *Tools) UploadFiles(r *http.Request, uploadDir string, rename ...bool) ([]*UploadedFile, error) { renameFile := true if len(rename) > 0 { @@ -151,3 +155,19 @@ func (t *Tools) CreateDirIfNotExist(dir string) error { } return nil } + +// Slugify transforms an input string into a URL-friendly slug by replacing non-alphanumeric characters with hyphens. +func (t *Tools) Slugify(s string) (string, error) { + if s == "" { + return "", errors.New("empty string not permitted") + } + var regEx = regexp.MustCompile(`[^a-z\d]+`) + + slug := strings.Trim(regEx.ReplaceAllString(strings.ToLower(s), "-"), "-") + + if len(slug) == 0 { + return "", errors.New("after removing characters, slug is zero length") + } + + return slug, nil +} diff --git a/tools_test.go b/tools_test.go index fbb7106..05e241d 100644 --- a/tools_test.go +++ b/tools_test.go @@ -13,6 +13,7 @@ import ( "testing" ) +// TestTools_RandomString verifies that the RandomString method returns a string of the correct length. func TestTools_RandomString(t *testing.T) { var testTools Tools s := testTools.RandomString(10) @@ -33,6 +34,7 @@ var uploadTests = []struct { {name: "not allowed", allowedTypes: []string{"image/jpeg"}, renameFile: false, errorExpected: true}, } +// TestTools_UploadFiles tests the file upload functionality via multipart form-data with various scenarios and configurations. func TestTools_UploadFiles(t *testing.T) { for _, e := range uploadTests { //set up a pipe to avoid buffering @@ -92,6 +94,7 @@ func TestTools_UploadFiles(t *testing.T) { } } +// TestTools_UploadOneFile tests the UploadOneFile method to ensure a file can be uploaded, stored, and verified correctly. func TestTools_UploadOneFile(t *testing.T) { //set up a pipe to avoid buffering pr, pw := io.Pipe() @@ -158,3 +161,32 @@ func TestTools_CreateDirIfNotExist(t *testing.T) { _ = os.Remove(fmt.Sprintf("./testdata/myDir")) } + +var slugTests = []struct { + name string + s string + expected string + errorExpected bool +}{ + {name: "valid string", s: "Hello World", expected: "hello-world", errorExpected: false}, + {name: "empty string", s: "", expected: "", errorExpected: true}, + {name: "complex string", s: "Now is the time for all GOOD men! + fish & such &^123", expected: "now-is-the-time-for-all-good-men-fish-such-123", errorExpected: false}, + {name: " japanese string", s: "こんにちは世界", expected: "", errorExpected: true}, + {name: " japanese string and roman characters", s: "hello world こんにちは世界", expected: "hello-world", errorExpected: false}, +} + +func TestTools_Slugify(t *testing.T) { + var testTools Tools + + for _, test := range slugTests { + slug, err := testTools.Slugify(test.s) + if err != nil && !test.errorExpected { + t.Errorf("%s: error received but none expected: %s", test.name, err.Error()) + } + + if !test.errorExpected && slug != test.expected { + t.Errorf("%s: wrong slug retrned; expected %s but got %s", test.name, test.expected, slug) + } + } + +}