Skip to content

Case Converter — Go source

Convert text between UPPER, lower, Title, Sentence, camelCase, PascalCase, snake_case, kebab-case, CONSTANT_CASE and slug. Live, one-click copy.

This is the Go implementation — the same logic the interactive tool runs, in a shareable, citable form.

// Package caseconverter is the Go twin of CosmoDev's src/lib/case.ts (dual
// source: the web lib is TypeScript, the CLI lib is Go — kept in lock-step).
// Pure + deterministic, never panics. The table-driven tests in
// case-converter_test.go share vectors with src/lib/case.test.ts so the two
// implementations are held to the same contract.
//
// Pipeline mirrors the TS lib exactly: split into words (insert a space at
// each lowercase/digit→uppercase boundary, split on any run of
// whitespace/underscore/hyphen/dot, strip remaining non-alphanumeric chars
// from each token, drop empties), then apply the requested casing.
package caseconverter

import (
	"regexp"
	"strings"
)

// camelBoundary inserts a space between a lowercase letter or digit and a
// following uppercase letter, mirroring /([a-z0-9])([A-Z])/g in the TS lib.
var camelBoundary = regexp.MustCompile(`([a-z0-9])([A-Z])`)

// delimRun is the word splitter: any run of whitespace, underscore, hyphen, or
// dot — mirroring /[\s_\-.]+/ in the TS lib.
var delimRun = regexp.MustCompile(`[\s_.\-]+`)

// nonAlnum matches a single non-alphanumeric char, mirroring /[^a-zA-Z0-9]/g.
var nonAlnum = regexp.MustCompile(`[^a-zA-Z0-9]`)

// wordRun matches a word char followed by any run of non-whitespace — the
// title transform's replacement target, mirroring /\w\S*/g in the TS lib.
var wordRun = regexp.MustCompile(`\w\S*`)

// Words splits text into clean alphanumeric words. It is the Go twin of
// words() in src/lib/case.ts: insert a space at each camelCase boundary, split
// on any run of whitespace/underscore/hyphen/dot, strip remaining
// non-alphanumeric chars from each token, then drop empties.
func Words(s string) []string {
	s = camelBoundary.ReplaceAllString(s, "$1 $2")
	parts := delimRun.Split(s, -1)
	out := make([]string, 0, len(parts))
	for _, w := range parts {
		w = nonAlnum.ReplaceAllString(w, "")
		if w != "" {
			out = append(out, w)
		}
	}
	return out
}

// Cap uppercases the first rune and lowercases the rest. It is the Go twin of
// cap() in src/lib/case.ts; the empty string is returned unchanged.
func Cap(s string) string {
	if s == "" {
		return ""
	}
	r := []rune(s)
	return strings.ToUpper(string(r[0])) + strings.ToLower(string(r[1:]))
}

// Upper returns s with all letters uppercased. Twin of caseTransforms.upper.
func Upper(s string) string { return strings.ToUpper(s) }

// Lower returns s with all letters lowercased. Twin of caseTransforms.lower.
func Lower(s string) string { return strings.ToLower(s) }

// Title uppercases the first letter and lowercases the rest of each
// whitespace-delimited token, preserving the original spacing/punctuation.
// Twin of caseTransforms.title (s.replace(/\w\S*/g, cap)).
func Title(s string) string {
	return wordRun.ReplaceAllStringFunc(s, Cap)
}

// Sentence lowercases and trims s, then uppercases its first letter. The
// empty string stays empty. Twin of caseTransforms.sentence.
func Sentence(s string) string {
	t := strings.ToLower(strings.TrimSpace(s))
	if t == "" {
		return ""
	}
	r := []rune(t)
	return strings.ToUpper(string(r[0])) + string(r[1:])
}

// Camel joins the words of s with the first lowercased and the rest capped.
// Twin of caseTransforms.camel.
func Camel(s string) string {
	ws := Words(s)
	for i, w := range ws {
		if i == 0 {
			ws[i] = strings.ToLower(w)
		} else {
			ws[i] = Cap(w)
		}
	}
	return strings.Join(ws, "")
}

// Pascal joins the words of s, each capped. Twin of caseTransforms.pascal.
func Pascal(s string) string {
	ws := Words(s)
	for i, w := range ws {
		ws[i] = Cap(w)
	}
	return strings.Join(ws, "")
}

// Snake joins the lowercased words of s with underscores. Twin of
// caseTransforms.snake.
func Snake(s string) string {
	return strings.ToLower(strings.Join(Words(s), "_"))
}

// Kebab joins the lowercased words of s with hyphens. Twin of
// caseTransforms.kebab.
func Kebab(s string) string {
	return strings.ToLower(strings.Join(Words(s), "-"))
}

// Constant joins the uppercased words of s with underscores. Twin of
// caseTransforms.constant.
func Constant(s string) string {
	return strings.ToUpper(strings.Join(Words(s), "_"))
}

// Slug joins the lowercased words of s with hyphens. Twin of
// caseTransforms.slug.
func Slug(s string) string {
	return strings.ToLower(strings.Join(Words(s), "-"))
}

// CaseId names a case transform, mirroring the CaseId union in
// src/lib/case.ts. The string value matches the TS object key.
type CaseId string

const (
	CaseUpper    CaseId = "upper"
	CaseLower    CaseId = "lower"
	CaseTitle    CaseId = "title"
	CaseSentence CaseId = "sentence"
	CaseCamel    CaseId = "camel"
	CasePascal   CaseId = "pascal"
	CaseSnake    CaseId = "snake"
	CaseKebab    CaseId = "kebab"
	CaseConstant CaseId = "constant"
	CaseSlug     CaseId = "slug"
)

// Transform applies the named case transform to s. It mirrors
// caseTransforms[id] in the TS lib. An unknown id returns ("", false) — never
// a panic, mirroring the TS keyed lookup returning undefined.
func Transform(id CaseId, s string) (string, bool) {
	switch id {
	case CaseUpper:
		return Upper(s), true
	case CaseLower:
		return Lower(s), true
	case CaseTitle:
		return Title(s), true
	case CaseSentence:
		return Sentence(s), true
	case CaseCamel:
		return Camel(s), true
	case CasePascal:
		return Pascal(s), true
	case CaseSnake:
		return Snake(s), true
	case CaseKebab:
		return Kebab(s), true
	case CaseConstant:
		return Constant(s), true
	case CaseSlug:
		return Slug(s), true
	}
	return "", false
}

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →