Skip to content

Latest commit

 

History

History
199 lines (183 loc) · 5.51 KB

case-predictor.md

File metadata and controls

199 lines (183 loc) · 5.51 KB

Case Predictor

Predicts whether a string is camelCase, PascalCase, snake_case or kebab-case. Useful for applying conversion logic, say if we have a program that converts all the cases to camelCase, but we want to optimize the transformation, kebabToCamelCase, pascalToCamelCase, snakeToCamelCase, anyToCamelCase or if the string is already camel case, e.g.

const caseType = predictCase(str)
switch (caseType) {
  case 'kebab': kebabToWhateverCase(str)
  ...
}
function stringScore(str = '') {
  const pascal = str.match(/^[A-Z]/) || []
  const camel = str.match(/[a-z][A-Z]/g) || []
  const kebab = str.match(/-/g) || []
  const snake = str.match(/_/g) || []
  return {
    pascal: pascal.length,
    camel: camel.length,
    kebab: kebab.length,
    snake: snake.length
  }
}

function caseScore(str, name) {
  const scores = stringScore(str)
  switch (name) {
    case 'kebab':
      return scores.kebab - scores.pascal - scores.snake - scores.camel
    case 'pascal':
      return (scores.pascal + scores.camel) - scores.kebab - scores.snake
    case 'snake':
      return scores.snake - scores.camel - scores.pascal - scores.kebab
    case 'camel':
      return scores.camel - (scores.pascal ? 1 : -1) - scores.kebab - scores.snake
    default:
      throw new Error(`"${name}" is not valid`)
  }
}
const CASES = 'camel kebab pascal snake'.split(' ')

function predictCase(str) {
  const scores = Object.fromEntries(CASES.map(caseType => [caseScore(str, caseType), caseType]))
  return scores[Math.max(...Object.keys(scores).map(Number))]
}

const words = ['this is amazing', 'this_is_amazing', 'thisIsAmazing', 'ThisIsAmazing', 'this-is-amazing', 'this-is amazing  sooo']
for (let word of words) {
  console.log(word, predictCase(word), normalizeCase(word))
}

function normalizeCase(str, delimiter = '-') {
  const pattern = new RegExp(`[^a-z0-9\${delimiter}]`, 'gi')
  const repeated = new RegExp(`${delimiter}+`, 'gi')
  const starting = new RegExp(`^${delimiter}+`, 'gi')
  const ending = new RegExp(`${delimiter}+$`, 'gi')
  return str.replace(pattern, delimiter).replace(repeated, delimiter).replace(starting, '').replace(ending, '')
}

class SnakeCase {
  #str = ''
  constructor(str) {
    this.#str = str
  }
  get camel() {
    const result = this.snake
    return result.charAt(0).toLowerCase() + result.substring(1).replace(/_[a-zA-Z0-9]/g, function(str) {
      return str.charAt(1).toUpperCase()
    })
  }
  get pascal() {
    return this.camel.charAt(0).toUpperCase() + this.camel.substring(1)
  }
  get kebab() {
    return this.snake.replace(/_/g, '-')
  }
  get snake() {
    const normalized = normalizeCase(this.#str.toLowerCase(), '_')
    return normalized
  }
}

class CamelCase {
  #str = ''
  constructor(str) {
    this.#str = str
  }
  get kebab() {
    const result = this.camel.replace(/[a-z][A-Z]/g, function(word) {
      return word.split('').join('-')
    })
    return result.toLowerCase()
  }
  get camel() {
    const result = this.#str.replace(/-[a-z0-9]/gi, function(match) {
      return match.charAt(1).toUpperCase()
    })
    return result.charAt(0).toLowerCase() + result.substring(1)
  }
  get pascal() {
    const result = this.camel
    return result.charAt(0).toUpperCase() + result.substring(1)
  }
  get snake() {
    return this.kebab.replace(/-/g, '_').toLowerCase()
  }
}


class PascalCase {
  #str = ''

  constructor(str) {
    this.#str = str
  }
  get camel() {
    const result = this.pascal
    return result.charAt(0).toLowerCase() + result.substring(1)
  }

  get pascal() {
    const normalize = normalizeCase(this.#str, '_')
    const result = normalize.replace(/_[a-z0-9]/gi, function(match) {
      return match.charAt(1).toUpperCase()
    })
    return result.charAt(0).toUpperCase() + result.substring(1)
  }

  get kebab() {
    const result = this.pascal
    return result.replace(/[a-z][A-Z]/g, function(char) {
      return char.toLowerCase().split('').join('-')
    }).toLowerCase()
  }
  get snake() {
    return this.kebab.replace(/-/g, '_').toLowerCase()
  }
}

class KebabCase {
  #str = ''
  constructor(str) {
    this.#str = str
  }
  get kebab() {
    const normalized = normalizeCase(this.#str, '-')
    return normalized.toLowerCase()
  }
  get snake() {
    return this.kebab.replace(/_/g, '-')
  }
  get camel() {
    const result = this.kebab.replace(/-[a-z]/gi, function(word) {
      return word.charAt(1).toUpperCase()
    })
    return result.charAt(0).toLowerCase() + result.substring(1)
  }
  get pascal() {
    return this.camel.charAt(0).toUpperCase() + this.camel.substring(1)
  }
}

class ObjectCase {
  #str = ''
  constructor(str) {
    this.#str = str
  }
  caseUp() {
    return this.#str.charAt(0).toUpperCase() + this.#str.substring(1)
  }
  caseDown() {
    return this.#str.charAt(0).toLowerCase() + this.#str.substring(1)
  }
}

for (let text of ['this-is-great', 'NiceOne!', 'nice_one_haha', 'niceONe']) {
  const caseType = predictCase(text)
  switch (caseType) {
    case 'snake':
      const snake = new SnakeCase(text)
      console.log('snake', snake.camel, snake.pascal, snake.kebab, snake.snake)
      break
    case 'camel':
      const camel = new CamelCase(text)
      console.log('camel', camel.camel, camel.pascal, camel.kebab, camel.snake)
      break
    case 'pascal':
      const pascal = new PascalCase(text)
      console.log('pascal', pascal.camel, pascal.pascal, pascal.kebab, pascal.snake)
      break
    case 'kebab':
      const kebab = new KebabCase(text)
      console.log('kebab', kebab.camel, kebab.pascal, kebab.kebab, kebab.snake)
      break
    default:
  }
}