Current section

Files

Jump to
oaspec src oaspec util naming.gleam
Raw

src/oaspec/util/naming.gleam

import gleam/dict
import gleam/int
import gleam/list
import gleam/regexp
import gleam/string
/// Convert a string to PascalCase for Gleam type names.
/// Examples: "pet_store" -> "PetStore", "get-user" -> "GetUser"
pub fn to_pascal_case(input: String) -> String {
input
|> split_words
|> list.map(capitalize)
|> string.join("")
}
/// Convert a string to snake_case for Gleam function/variable names.
/// Examples: "PetStore" -> "pet_store", "getUserById" -> "get_user_by_id"
/// Gleam keywords are suffixed with _ to avoid syntax errors.
pub fn to_snake_case(input: String) -> String {
let result =
input
|> insert_underscores_before_caps
|> split_words
|> list.map(string.lowercase)
|> string.join("_")
escape_keyword(result)
}
/// Gleam reserved keywords that cannot be used as identifiers.
fn escape_keyword(name: String) -> String {
case name {
"as"
| "assert"
| "auto"
| "case"
| "const"
| "external"
| "fn"
| "if"
| "import"
| "let"
| "opaque"
| "panic"
| "pub"
| "test"
| "todo"
| "type"
| "use" -> name <> "_"
_ -> name
}
}
/// Convert an OpenAPI operation ID to a valid Gleam function name.
pub fn operation_to_function_name(operation_id: String) -> String {
operation_id
|> to_snake_case
}
/// Convert an OpenAPI schema name to a valid Gleam type name.
pub fn schema_to_type_name(schema_name: String) -> String {
schema_name
|> to_pascal_case
}
/// Convert an OpenAPI schema name to a valid Gleam module name.
pub fn to_module_name(name: String) -> String {
name
|> to_snake_case
}
/// Capitalize the first letter of a string.
pub fn capitalize(input: String) -> String {
case string.pop_grapheme(input) {
Ok(#(first, rest)) -> string.uppercase(first) <> rest
Error(_) -> input
}
}
/// Split a string into words by common separators.
fn split_words(input: String) -> List(String) {
let assert Ok(re) = regexp.from_string("[_\\-\\s./]+")
let parts = regexp.split(re, input)
parts
|> list.flat_map(split_camel_case)
|> list.filter(fn(s) { s != "" })
}
/// Split camelCase/PascalCase into separate words.
fn split_camel_case(input: String) -> List(String) {
let assert Ok(re) =
regexp.from_string("([A-Z]+(?=[A-Z][a-z])|[A-Z]?[a-z]+|[A-Z]+|[0-9]+)")
let matches = regexp.scan(re, input)
case matches {
[] -> [input]
_ ->
list.map(matches, fn(m) {
let regexp.Match(content, ..) = m
content
})
}
}
/// Insert underscores before capital letters in camelCase strings.
fn insert_underscores_before_caps(input: String) -> String {
let assert Ok(re) = regexp.from_string("([a-z0-9])([A-Z])")
regexp.replace(re, input, "\\1_\\2")
}
/// Deduplicate a list of names by appending _2, _3, etc. to duplicates.
/// Preserves order. First occurrence keeps original name.
pub fn deduplicate_names(names: List(String)) -> List(String) {
let #(result_rev, _) =
list.fold(names, #([], dict.new()), fn(acc, name) {
let #(result, counts) = acc
case dict.get(counts, name) {
Error(_) -> {
let counts = dict.insert(counts, name, 1)
#([name, ..result], counts)
}
Ok(count) -> {
let new_count = count + 1
let unique_name = name <> "_" <> int.to_string(new_count)
let counts = dict.insert(counts, name, new_count)
#([unique_name, ..result], counts)
}
}
})
list.reverse(result_rev)
}