Skip to content

Instantly share code, notes, and snippets.

@TakesxiSximada
Last active January 8, 2023 13:04
Show Gist options
  • Save TakesxiSximada/e8a10244aac6308de1323d1f6685658b to your computer and use it in GitHub Desktop.
Save TakesxiSximada/e8a10244aac6308de1323d1f6685658b to your computer and use it in GitHub Desktop.
change-case.el - Transform a string between camelCase, PascalCase, snake_case, kebab-case, doted.case and others by Emacs Lisp.

THIS REPOSITORY WAS MOVED TO https://github.com/TakesxiSximada/change-case.el

change-case.el

Transform a string between camelCase, PascalCase, snake_case, kebab-case, doted.case and others by Emacs Lisp. Inspired by https://github.com/blakeembrey/change-case/ .

Screenshot

example

Supported case

  • dotted.case
  • path/case
  • snake_case
  • kebab-case
  • PascalCase
  • camelCase

Dependencies

  • dash
  • s
  • ert

Install

This package is not yet melpa packaging.

Install by quelpa

(require 'quelpa)

(quelpa '(change-case :fetcher git :url "git@gist.github.com:e8a10244aac6308de1323d1f6685658b.git"))

See here for how to install quelpa itself https://github.com/quelpa/quelpa#installing-packages .

Install by package.el

  1. Download change-case.el.
  2. M-x package-install-file RET /PATH/TO/DOWNLOADED/change-case.el RET

Testing

Using ert.

  1. Open change-case.el file.
  2. Run M-x ert on change-case.el buffer.

Output example::

Selector: t
Passed:  13
Failed:  0
Skipped: 0
Total:   13/13

Started at:   2020-04-29 15:28:31+0900
Finished.
Finished at:  2020-04-29 15:28:31+0900

.............

Bug report and Contributing

We haven't prepared those flows yet. For the time being, please write the steps to reproduce the bug in this gist comment, or attach the patch file in diff format. Welcome them.

;;; change-case-test.el
;; Copyright (C) 2020 TakesxiSximada
;; This package is distributed in the hope that it will be useful, but WITHOUT
;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
;; License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with change-case.el. If not, see http://www.gnu.org/licenses.
;;; Code:
(require 'ert)
(require 'change-case)
;; test
(ert-deftest change-case-dotted-case-parse-test ()
(should
(equal '("change" "case" "el")
(change-case-dotted-case-parse "change.case.el"))))
(ert-deftest change-case-dotted-case-renderer-test ()
(should
(string-equal "change.case.el"
(change-case-dotted-case-render '("change" "case" "el")))))
;; test
(ert-deftest change-case-path-case-parse-test ()
(should
(equal '("change" "case" "el")
(change-case-path-case-parse "change/case/el"))))
(ert-deftest change-case-path-case-renderer-test ()
(should
(string-equal "change/case/el"
(change-case-path-case-render '("change" "case" "el")))))
;; test
(ert-deftest change-case-snake-case-parse-test ()
(should
(equal '("change" "case" "el")
(change-case-snake-case-parse "change_case_el"))))
(ert-deftest change-case-snake-case-renderer-test ()
(should
(string-equal "change_case_el"
(change-case-snake-case-render '("change" "case" "el")))))
;; test
(ert-deftest change-case-kebab-case-parse-test ()
(should
(equal '("change" "case" "el")
(change-case-kebab-case-parse "change-case-el"))))
(ert-deftest change-case-kebab-case-renderer-test ()
(should
(string-equal "change-case-el"
(change-case-kebab-case-render '("change" "case" "el")))))
;; test
(ert-deftest change-case-pascal-case-parse-test ()
(should
(equal '("Change" "Case" "El")
(change-case-pascal-case-parse "ChangeCaseEl"))))
(ert-deftest change-case-pascal-case-renderer-test ()
(should
(string-equal "ChangeCaseEl"
(change-case-pascal-case-render '("change" "case" "el")))))
;; test
(ert-deftest change-case-camel-case-parse-test ()
(should
(equal '("change" "Case" "El")
(change-case-camel-case-parse "changeCaseEl"))))
(ert-deftest change-case-camel-case-renderer-test ()
(should
(string-equal "changeCaseEl"
(change-case-camel-case-render '("change" "case" "el")))))
(provide 'change-case-test)
;;; change-case.el ends here
;;; change-case.el --- Case conversion between camelCase, PascalCase, snake_case and more -*- lexical-binding: t -*-
;; Copyright (C) 2020 TakesxiSximada
;; Author: TakesxiSximada <8707279+TakesxiSximada@users.noreply.github.com>
;; Maintainer: TakesxiSximada <8707279+TakesxiSximada@users.noreply.github.com>
;; Repository: https://gist.github.com/TakesxiSximada/e8a10244aac6308de1323d1f6685658b
;; Version: 9
;; Package-Version: 20210220.0000
;; Package-Requires: ((emacs "25.1") (dash "2.16.0") (s "1.12.0"))
;; Date: 2021-02-20
;; This package is free software; you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.
;;
;; The change-case.el is distributed in the hope that it will be useful, but WITHOUT
;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
;; License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with change-case.el. If not, see http://www.gnu.org/licenses.
;;; Commentary:
;; Transform a string between camelCase, PascalCase, snake_case, kebab-case, doted.case
;; and others by Emacs Lisp.
;;; Code:
(require 'rx)
(require 'dash)
(require 's)
;;; Options
(defgroup change-case nil
"Case conversion."
:prefix "change-case")
;;; dotted.case
(defvar change-case-dotted-case-separator "."
"Used as delimiter in doted case.")
(defun change-case-dotted-case-parse (sentence)
(s-split (regexp-quote change-case-dotted-case-separator)
sentence))
(defun change-case-dotted-case-render (word-list)
(string-join word-list change-case-dotted-case-separator))
;;; path/case
(defvar change-case-path-case-separator "/"
"Used as delimiter in path case.")
(defun change-case-path-case-parse (sentence)
(s-split change-case-path-case-separator sentence))
(defun change-case-path-case-render (word-list)
(string-join word-list change-case-path-case-separator))
;;; snake_case
(defvar change-case-snake-case-separator "_"
"Used as delimiter in snake case.")
(defun change-case-snake-case-parse (sentence)
(s-split change-case-snake-case-separator sentence))
(defun change-case-snake-case-render (word-list)
(string-join (mapcar 'downcase word-list)
change-case-snake-case-separator))
;;; kebab-case
(defvar change-case-kebab-case-separator "-"
"Used as delimiter in kebab case.")
(defun change-case-kebab-case-parse (sentence)
(s-split change-case-kebab-case-separator sentence))
(defun change-case-kebab-case-render (word-list)
(string-join (mapcar 'downcase word-list) change-case-kebab-case-separator))
;;; PascalCase
(defun change-case--get-pascal-case-inner-partition (sentence &optional start)
(let ((case-fold-search nil))
(if-let ((pos (string-match (rx upper-case) sentence (or start 1))))
(cons pos
(change-case--get-pascal-case-inner-partition sentence (+ pos 1))))))
(defun change-case--get-pascal-case-partition (sentence &optional start)
(cons 0
(append (change-case--get-pascal-case-inner-partition sentence start)
(cons (length sentence) nil))))
(defun change-case--get-pair-list (sequence)
(if-let ((start (nth 0 sequence))
(end (nth 1 sequence)))
(cons
(cons start end)
(change-case--get-pair-list (cdr sequence)))))
(defun change-case-pascal-case-parse (sentence)
(mapcar
(lambda (pair) (substring sentence
(car pair)
(cdr pair)))
(change-case--get-pair-list
(change-case--get-pascal-case-partition sentence))))
(defun change-case-pascal-case-render (word-list)
(string-join (mapcar 'capitalize word-list)))
;;; camelCase
(defun change-case-camel-case-parse (sentence)
(change-case-pascal-case-parse sentence))
(defun change-case-camel-case-render (word-list)
(concat
(downcase (car word-list))
(change-case-pascal-case-render (cdr word-list))))
;;; Options
(defvar change-case-parser-alist
'(("dotted.case" . change-case-dotted-case-parse)
("path/case" . change-case-path-case-parse)
("snake_case" . change-case-snake-case-parse)
("kebab-case" . change-case-kebab-case-parse)
("PascalCase" . change-case-pascal-case-parse)
("camelCase" . change-case-camel-case-parse))
"List of valid parser functions.")
(defvar change-case-renderer-alist
'(("dotted.case" . change-case-dotted-case-render)
("path/case" . change-case-path-case-render)
("snake_case" . change-case-snake-case-render)
("kebab-case" . change-case-kebab-case-render)
("PascalCase" . change-case-pascal-case-render)
("camelCase" . change-case-camel-case-render))
"List of valid render functions.")
;;; U/I
(defcustom change-case-parser-prompt "change-case: parser:"
"Prompt used in the parser function selection UI."
:group 'change-case)
(defcustom change-case-renderer-prompt "change-case: renderer:"
"Prompt used in the render function selection UI."
:group 'change-case)
(defcustom change-case-parser-default "dotted.case"
"Default parser."
:group 'change-case)
(defcustom change-case-renderer-default "dotted.case"
"Default renderer."
:group 'change-case)
(defun change-case-select-ui (prompt choices default)
(ido-completing-read prompt
choices
nil nil nil nil
default))
(defun change-case-select-parser ()
(cdr (assoc (change-case-select-ui change-case-parser-prompt
(mapcar 'car change-case-parser-alist)
change-case-parser-default)
change-case-parser-alist)))
(defun change-case-select-renderer ()
(cdr (assoc (change-case-select-ui change-case-renderer-prompt
(mapcar 'car change-case-renderer-alist)
change-case-renderer-default)
change-case-renderer-alist)))
(defun change-case-edit (start end sentence)
(delete-region start end)
(save-excursion
(goto-char start)
(insert sentence)))
(defun change-case-update-parser-and-renderer-default (parser renderer)
(setq change-case-parser-default (car (rassoc parser change-case-parser-alist)))
(setq change-case-renderer-default (car (rassoc renderer change-case-renderer-alist))))
;;;###autoload
(defun change-case (&optional start end)
(interactive (if (use-region-p)
(list (region-beginning) (region-end))
(if-let (bounds (bounds-of-thing-at-point 'symbol))
(list (car bounds) (cdr bounds))
(list (point-min (point-max))))))
(let ((sentence (buffer-substring-no-properties start end))
(parser (change-case-select-parser))
(renderer (change-case-select-renderer)))
(change-case-edit
start end
(funcall renderer (funcall parser sentence)))
(change-case-update-parser-and-renderer-default parser
renderer)))
;;; _
(provide 'change-case)
;;; change-case.el ends here

Release Notes

Release Versions

Version 9 (2021-02-20)

  • Fixed to failed pascal case conversion functions.

Version 8 (2020-05-06)

  • Update docstring.
  • Update example GIF animation.

Version 7 (2020-05-02)

  • Fixed pascal case bug.

Version 6 (2020-04-28)

  • Fixed pascal case pattern bug.
  • Fixed to get bounds by `bounds-of-things-at-point` if no bounds are specified.

Version 5 (2020-04-26)

  • Added defgroup and descriptions.

Version 4 (2020-04-26)

  • Added ability to remember previous parsing and renderers.

Version 3 (2020-04-26)

  • Added ert tests.
  • Added Release note.

Version 2 (2020-04-25)

  • Added path/case.
  • Added dotted/case.

Version 1 (2020-04-25)

  • First implements.
  • Supported case.
    • snake_case
    • kebab_case
    • PascalCase
    • camelCase
@vlagorsse
Copy link

vlagorsse commented Jun 17, 2021

Hi,
First thank you for your nice package.

I got an issue with it. When I start from parsing either camelCase either PascalCase, I'm facing this issue:

change-case--get-pascal-case-inner-partition: Invalid function: (pos (string-match (rx upper-case) sentence (or start 1)))    

I'm running with emacs 26.3.

@TakesxiSximada
Copy link
Author

@vlagorsse
Thank you for your bug report.
I will investigate this issue.

@TakesxiSximada
Copy link
Author

TakesxiSximada commented Nov 28, 2021

I am using emacs version 28.0.50 (self build from source) usually. I
had not emacs 26.3 in my machine, therefore installed emacs26.3 in my
machine. and tried to reproduct of the issue, I could not it. I was
confirmed the following behavior.

;; basic test
(change-case--get-pascal-case-inner-partition "Testing" 0)
=> (0)

(change-case--get-pascal-case-inner-partition "Testing" 1)
=> nil


(change-case--get-pascal-case-inner-partition "Testing" 2)
=> nil


(change-case--get-pascal-case-inner-partition "tesTing" 0)
=> (3)


(change-case--get-pascal-case-inner-partition "tesTing" 1)
=> (3)

(change-case--get-pascal-case-inner-partition "tesTing" 2)
=> (3)

(change-case--get-pascal-case-inner-partition "tesTing" 3)
=> (3)

(change-case--get-pascal-case-inner-partition "tesTing" 4)
=> nil


;; number test 
(change-case--get-pascal-case-inner-partition "1tesTing" 0)
=> (4)

;; white space test
(change-case--get-pascal-case-inner-partition " tesTing" 0)
=> (4)

;; multi byte character test
(change-case--get-pascal-case-inner-partition "日本語 tesTing" 0)
=> (7)

I can to research about this issue again, if you give me something
hints about it.

Thanks.

@eliascotto
Copy link

Thanks for the functions!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment