Skip to content

Commit

Permalink
Update strcase docs, test harness, and other bit rot (#10)
Browse files Browse the repository at this point in the history
* Update docs
* Remove testify as dependency
* Update test harness
* update benchmark dependencies
* Update stdlib benchmark
* Don't lint benchmark code
  • Loading branch information
liyanchang authored Jan 13, 2023
1 parent 26d5b1a commit c8ac0fd
Show file tree
Hide file tree
Showing 20 changed files with 353 additions and 259 deletions.
16 changes: 8 additions & 8 deletions .github/workflows/golangci-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ on:
branches:
- main
pull_request:
permissions:
contents: read
jobs:
golangci:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: golangci-lint
uses: golangci/golangci-lint-action@v1
- uses: actions/setup-go@v3
with:
version: v1.26
- name: golangci-lint-benchmark
uses: golangci/golangci-lint-action@v1
go-version: 1.19
- uses: actions/checkout@v3
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: v1.26
args: ./benchmark/*.go
version: v1.50
4 changes: 2 additions & 2 deletions .github/workflows/gotest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
test:
strategy:
matrix:
go-version: [1.13.x, 1.14.x]
go-version: [1.18.x, 1.19.x]
platform: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.platform }}
steps:
Expand All @@ -29,7 +29,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: 1.14.x
go-version: 1.19.x
- name: Checkout code
uses: actions/checkout@v2
- name: Benchmark
Expand Down
8 changes: 1 addition & 7 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ linters-settings:
- ifElseChain
- whyNoLint
- wrapperFunc
golint:
min-confidence: 0.5
govet:
check-shadowing: true
lll:
Expand All @@ -37,7 +35,6 @@ linters:
disable-all: true
enable:
- bodyclose
- deadcode
- depguard
- dogsled
- dupl
Expand All @@ -47,26 +44,23 @@ linters:
- gocyclo
- gofmt
- goimports
- golint
- goprintffuncname
- gosec
- gosimple
- govet
- ineffassign
- interfacer
- lll
- misspell
- nakedret
- nolintlint
- revive
- rowserrcheck
- staticcheck
- structcheck
- stylecheck
- typecheck
- unconvert
- unparam
- unused
- varcheck
- whitespace

# don't enable:
Expand Down
8 changes: 4 additions & 4 deletions .readme.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ Convert strings to `snake_case`, `camelCase`, `PascalCase`, `kebab-case` and mor
## <a name="pkg-index">Index</a>{{if .Consts}}
* [Constants](#pkg-constants){{end}}{{if .Vars}}
* [Variables](#pkg-variables){{end}}{{- range .Funcs -}}{{$name_html := html .Name}}
* [{{node_html $ .Decl false | sanitize}}](#{{$name_html}}){{- end}}{{- range .Types}}{{$tname_html := html .Name}}
* [type {{$tname_html}}](#{{$tname_html}}){{- range .Funcs}}{{$name_html := html .Name}}
* [{{node_html $ .Decl false | sanitize}}](#{{$name_html}}){{- end}}{{- range .Methods}}{{$name_html := html .Name}}
* [{{node_html $ .Decl false | sanitize}}](#{{$tname_html}}.{{$name_html}}){{- end}}{{- end}}{{- if $.Notes}}{{- range $marker, $item := $.Notes}}
* [{{node_html $ .Decl false | sanitize}}](#func-{{$name_html}}){{- end}}{{- range .Types}}{{$tname_html := html .Name}}
* [type {{$tname_html}}](#type-{{$tname_html}}){{- range .Funcs}}{{$name_html := html .Name}}
* [{{node_html $ .Decl false | sanitize}}](#func-{{$name_html}}){{- end}}{{- range .Methods}}{{$name_html := html .Name}}
* [{{node_html $ .Decl false | sanitize}}](#type-{{$tname_html}}.{{$name_html}}){{- end}}{{- end}}{{- if $.Notes}}{{- range $marker, $item := $.Notes}}
* [{{noteTitle $marker | html}}s](#pkg-note-{{$marker}}){{end}}{{end}}
{{if $.Examples}}
#### <a name="pkg-examples">Examples</a>{{- range $.Examples}}
Expand Down
9 changes: 6 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
.PHONY: benchmark docs lint test

docs:
which godoc2ghmd || ( go get github.com/DevotedHealth/godoc2ghmd && go mod tidy )
which godoc2ghmd || go get github.com/DevotedHealth/godoc2ghmd
godoc2ghmd -template .readme.tmpl github.com/ettle/strcase > README.md
go mod tidy

test:
go test -cover ./...

lint:
which golangci-lint || ( go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.27.0 && go mod tidy )
which golangci-lint || go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.50.1
golangci-lint run
golangci-lint run benchmark/*.go
go mod tidy

benchmark:
cd benchmark && go test -bench=. -test.benchmem && go mod tidy
cd benchmark && go test -bench=. -test.benchmem
go mod tidy
134 changes: 71 additions & 63 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,24 @@ Example usage
strcase.ToSnake("FOOBar") // foo_bar

// Support Go initialisms
strcase.ToGoCamel("http_response") // HTTPResponse
strcase.ToGoPascal("http_response") // HTTPResponse

// Specify case and delimiter
strcase.ToCase("HelloWorld", strcase.UpperCase, '.') // HELLO.WORLD

### Why this package
## Why this package

String strcase is pretty straight forward and there are a number of methods to
do it. This package is fully featured, more customizable, better tested, and
faster* than other packages and what you would probably whip up yourself.

### Unicode support

We work for with unicode strings and pay very little performance penalty for it
as we optimized for the common use case of ASCII only strings.

### Customization

You can create a custom caser that changes the behavior to what you want. This
customization also reduces the pressure for us to change the default behavior
which means that things are more stable for everyone involved. The goal is to
Expand All @@ -71,19 +74,22 @@ make the common path easy and fast, while making the uncommon path possible.
assert.Equal(t, "http_200", c.ToSnake("http200"))

### Initialism support

By default, we use the golint intialisms list. You can customize and override
the initialisms if you wish to add additional ones, such as "SSL" or "CMS" or
domain specific ones to your industry.


ToGoCamel("http_response") // HTTPResponse
ToGoPascal("http_response") // HTTPResponse
ToGoSnake("http_response") // HTTP_response

### Test coverage

We have a wide ranging test suite to make sure that we understand our behavior.
Test coverage isn't everything, but we aim for 100% coverage.

### Fast

Optimized to reduce memory allocations with Builder. Benchmarked and optimized
around common cases.

Expand All @@ -96,56 +102,54 @@ Hopefully I was fair to each library and happy to rerun benchmarks differently
or reword my commentary based on suggestions or updates.


// This package
// Go intialisms and custom casers are slower
BenchmarkToTitle-4 992491 1559 ns/op 32 B/op 1 allocs/op
BenchmarkToSnake-4 1000000 1475 ns/op 32 B/op 1 allocs/op
BenchmarkToSNAKE-4 1000000 1609 ns/op 32 B/op 1 allocs/op
BenchmarkToGoSnake-4 275010 3697 ns/op 44 B/op 4 allocs/op
BenchmarkToCustomCaser-4 342704 4191 ns/op 56 B/op 4 allocs/op
// This package - faster then almost all libraries
// Initialisms are more complicated and slightly slower, but still faster then other libraries that do less
BenchmarkToTitle-4 7821166 221 ns/op 32 B/op 1 allocs/op
BenchmarkToSnake-4 9378589 202 ns/op 32 B/op 1 allocs/op
BenchmarkToSNAKE-4 6174453 223 ns/op 32 B/op 1 allocs/op
BenchmarkToGoSnake-4 3114266 434 ns/op 44 B/op 4 allocs/op
BenchmarkToCustomCaser-4 2973855 448 ns/op 56 B/op 4 allocs/op

// Segment has very fast snake case and camel case libraries
// No features or customization, but very very fast
BenchmarkSegment-4 1303809 938 ns/op 16 B/op 1 allocs/op
BenchmarkSegment-4 24003495 64.9 ns/op 16 B/op 1 allocs/op

// Stdlib strings.Title for comparison, even though it only splits on spaces
BenchmarkToTitleStrings-4 1213467 1164 ns/op 16 B/op 1 allocs/op
BenchmarkToTitleStrings-4 11259376 161 ns/op 16 B/op 1 allocs/op

// Other libraries or code snippets
// - Most are slower, by up to an order of magnitude
// - None support initialisms or customization
// - Some generate only camelCase or snake_case
// - Many lack unicode support
BenchmarkToSnakeStoewer-4 973200 2075 ns/op 64 B/op 2 allocs/op
BenchmarkToSnakeStoewer-4 7103268 297 ns/op 64 B/op 2 allocs/op
// Copying small rune arrays is slow
BenchmarkToSnakeSiongui-4 264315 4229 ns/op 48 B/op 10 allocs/op
BenchmarkGoValidator-4 206811 5152 ns/op 184 B/op 9 allocs/op
BenchmarkToSnakeSiongui-4 3710768 413 ns/op 48 B/op 10 allocs/op
BenchmarkGoValidator-4 2416479 1049 ns/op 184 B/op 9 allocs/op
// String alloction is slow
BenchmarkToSnakeFatih-4 82675 12280 ns/op 392 B/op 26 allocs/op
BenchmarkToSnakeIanColeman-4 83276 13903 ns/op 145 B/op 13 allocs/op
BenchmarkToSnakeFatih-4 1000000 2407 ns/op 624 B/op 26 allocs/op
BenchmarkToSnakeIanColeman-4 1005766 1426 ns/op 160 B/op 13 allocs/op
// Regexp is slow
BenchmarkToSnakeGolangPrograms-4 74448 18586 ns/op 176 B/op 11 allocs/op
BenchmarkToSnakeGolangPrograms-4 614689 2237 ns/op 225 B/op 11 allocs/op

// These results aren't a surprise - my initial version of this library was
// painfully slow. I think most of us, without spending some time with
// profilers and benchmarks, would write also something on the slower side.

### Why not this package
### Zero dependencies

That's right - zero. We only import Go standard library. No hassles with
dependencies, licensing, security alerts.

## Why not this package

If every nanosecond matters and this is used in a tight loop, use segment.io's
libraries (<a href="https://github.com/segmentio/go-snakecase">https://github.com/segmentio/go-snakecase</a> and
<a href="https://github.com/segmentio/go-camelcase">https://github.com/segmentio/go-camelcase</a>). They lack features, but make up for
it by being blazing fast. Alternatively, if you need your code to work slightly
differently, fork them and tailor it for your use case.
it by being blazing fast.

If you don't like having external imports, I get it. This package only imports
packages for testing, otherwise it only uses the standard library. If that's
not enough, you can use this repo as the foundation for your own. MIT Licensed.
## Migrating from other packages

This package is still relatively new and while I've used it for a while
personally, it doesn't have the miles that other packages do. I've tested this
code agains't their test cases to make sure that there aren't any surprises.

### Migrating from other packages
If you are migrating from from another package, you may find slight differences
in output. To reduce the delta, you may find it helpful to use the following
custom casers to mimic the behavior of the other package.
Expand All @@ -161,32 +165,32 @@ custom casers to mimic the behavior of the other package.


## <a name="pkg-index">Index</a>
* [func ToCamel(s string) string](#ToCamel)
* [func ToCase(s string, wordCase WordCase, delimiter rune) string](#ToCase)
* [func ToGoCamel(s string) string](#ToGoCamel)
* [func ToGoCase(s string, wordCase WordCase, delimiter rune) string](#ToGoCase)
* [func ToGoKebab(s string) string](#ToGoKebab)
* [func ToGoPascal(s string) string](#ToGoPascal)
* [func ToGoSnake(s string) string](#ToGoSnake)
* [func ToKEBAB(s string) string](#ToKEBAB)
* [func ToKebab(s string) string](#ToKebab)
* [func ToPascal(s string) string](#ToPascal)
* [func ToSNAKE(s string) string](#ToSNAKE)
* [func ToSnake(s string) string](#ToSnake)
* [type Caser](#Caser)
* [func NewCaser(goInitialisms bool, initialismOverrides map[string]bool, splitFn SplitFn) *Caser](#NewCaser)
* [func (c *Caser) ToCamel(s string) string](#Caser.ToCamel)
* [func (c *Caser) ToCase(s string, wordCase WordCase, delimiter rune) string](#Caser.ToCase)
* [func (c *Caser) ToKEBAB(s string) string](#Caser.ToKEBAB)
* [func (c *Caser) ToKebab(s string) string](#Caser.ToKebab)
* [func (c *Caser) ToPascal(s string) string](#Caser.ToPascal)
* [func (c *Caser) ToSNAKE(s string) string](#Caser.ToSNAKE)
* [func (c *Caser) ToSnake(s string) string](#Caser.ToSnake)
* [type SplitAction](#SplitAction)
* [type SplitFn](#SplitFn)
* [func NewSplitFn(delimiters []rune, splitOptions ...SplitOption) SplitFn](#NewSplitFn)
* [type SplitOption](#SplitOption)
* [type WordCase](#WordCase)
* [func ToCamel(s string) string](#func-ToCamel)
* [func ToCase(s string, wordCase WordCase, delimiter rune) string](#func-ToCase)
* [func ToGoCamel(s string) string](#func-ToGoCamel)
* [func ToGoCase(s string, wordCase WordCase, delimiter rune) string](#func-ToGoCase)
* [func ToGoKebab(s string) string](#func-ToGoKebab)
* [func ToGoPascal(s string) string](#func-ToGoPascal)
* [func ToGoSnake(s string) string](#func-ToGoSnake)
* [func ToKEBAB(s string) string](#func-ToKEBAB)
* [func ToKebab(s string) string](#func-ToKebab)
* [func ToPascal(s string) string](#func-ToPascal)
* [func ToSNAKE(s string) string](#func-ToSNAKE)
* [func ToSnake(s string) string](#func-ToSnake)
* [type Caser](#type-Caser)
* [func NewCaser(goInitialisms bool, initialismOverrides map[string]bool, splitFn SplitFn) *Caser](#func-NewCaser)
* [func (c *Caser) ToCamel(s string) string](#type-Caser.ToCamel)
* [func (c *Caser) ToCase(s string, wordCase WordCase, delimiter rune) string](#type-Caser.ToCase)
* [func (c *Caser) ToKEBAB(s string) string](#type-Caser.ToKEBAB)
* [func (c *Caser) ToKebab(s string) string](#type-Caser.ToKebab)
* [func (c *Caser) ToPascal(s string) string](#type-Caser.ToPascal)
* [func (c *Caser) ToSNAKE(s string) string](#type-Caser.ToSNAKE)
* [func (c *Caser) ToSnake(s string) string](#type-Caser.ToSnake)
* [type SplitAction](#type-SplitAction)
* [type SplitFn](#type-SplitFn)
* [func NewSplitFn(delimiters []rune, splitOptions ...SplitOption) SplitFn](#func-NewSplitFn)
* [type SplitOption](#type-SplitOption)
* [type WordCase](#type-WordCase)



Expand All @@ -201,26 +205,28 @@ Also known as lowerCamelCase or mixedCase.



## <a name="ToCase">func</a> [ToCase](./strcase.go#L70)
## <a name="ToCase">func</a> [ToCase](./strcase.go#L72)
``` go
func ToCase(s string, wordCase WordCase, delimiter rune) string
```
ToCase returns words in given case and delimiter.



## <a name="ToGoCamel">func</a> [ToGoCamel](./strcase.go#L65)
## <a name="ToGoCamel">func</a> [ToGoCamel](./strcase.go#L67)
``` go
func ToGoCamel(s string) string
```
ToGoCamel returns words in camelCase (capitalized words concatenated together, with first word lower case).
Also known as lowerCamelCase or mixedCase.

Respects Go's common initialisms (e.g. httpResponse -> HTTPResponse).
Respects Go's common initialisms, but first word remains lowercased which is
important for code generator use cases (e.g. toJson -> toJSON, httpResponse
-> httpResponse).



## <a name="ToGoCase">func</a> [ToGoCase](./strcase.go#L77)
## <a name="ToGoCase">func</a> [ToGoCase](./strcase.go#L79)
``` go
func ToGoCase(s string, wordCase WordCase, delimiter rune) string
```
Expand Down Expand Up @@ -415,7 +421,7 @@ ToSnake returns words in snake_case (lower case words with underscores).



## <a name="SplitAction">type</a> [SplitAction](./split.go#L110)
## <a name="SplitAction">type</a> [SplitAction](./split.go#L111)
``` go
type SplitAction int
```
Expand Down Expand Up @@ -457,7 +463,7 @@ SplitFn defines how to split a string into words



### <a name="NewSplitFn">func</a> [NewSplitFn](./split.go#L14-L17)
### <a name="NewSplitFn">func</a> [NewSplitFn](./split.go#L15-L18)
``` go
func NewSplitFn(
delimiters []rune,
Expand All @@ -469,13 +475,12 @@ NewSplitFn returns a SplitFn based on the options provided.
NewSplitFn covers the majority of common options that other strcase
libraries provide and should allow you to simply create a custom caser.
For more complicated use cases, feel free to write your own SplitFn
nolint:gocyclo





## <a name="SplitOption">type</a> [SplitOption](./split.go#L93)
## <a name="SplitOption">type</a> [SplitOption](./split.go#L94)
``` go
type SplitOption int
```
Expand Down Expand Up @@ -524,6 +529,9 @@ const (
// TitleCase - Only first letter upper cased (Example)
TitleCase
// CamelCase - TitleCase except lower case first word (exampleText)
// Notably, even if the first word is an initialism, it will be lower
// cased. This is important for code generators where capital letters
// mean exported functions. i.e. jsonString(), not JSONString()
CamelCase
)
```
Expand Down
Loading

0 comments on commit c8ac0fd

Please sign in to comment.