Personal emacs config
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

220 lines
7.0 KiB

  1. ;;; json-reformat.el --- Reformatting tool for JSON
  2. ;; Author: Wataru MIYAGUNI <gonngo@gmail.com>
  3. ;; URL: https://github.com/gongo/json-reformat
  4. ;; Package-Version: 0.0.6
  5. ;; Package-Commit: b9bd375ec1deb10d2ba09c409bdcf99c56d7a716
  6. ;; Version: 0.0.6
  7. ;; Keywords: json
  8. ;; Copyright (c) 2012 Wataru MIYAGUNI
  9. ;;
  10. ;; MIT License
  11. ;;
  12. ;; Permission is hereby granted, free of charge, to any person obtaining
  13. ;; a copy of this software and associated documentation files (the
  14. ;; "Software"), to deal in the Software without restriction, including
  15. ;; without limitation the rights to use, copy, modify, merge, publish,
  16. ;; distribute, sublicense, and/or sell copies of the Software, and to
  17. ;; permit persons to whom the Software is furnished to do so, subject to
  18. ;; the following conditions:
  19. ;;
  20. ;; The above copyright notice and this permission notice shall be
  21. ;; included in all copies or substantial portions of the Software.
  22. ;;
  23. ;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  24. ;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  25. ;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  26. ;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  27. ;; LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  28. ;; OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  29. ;; WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  30. ;;; Commentary:
  31. ;; json-reformat.el is a reformatting tool for JSON (http://json.org/).
  32. ;;
  33. ;; ## Usage
  34. ;;
  35. ;; 1. Specify region
  36. ;; 2. Call 'M-x json-reformat-region'
  37. ;;
  38. ;; ## Customize
  39. ;;
  40. ;; - `json-reformat:indent-width'
  41. ;; - `json-reformat:pretty-string?'
  42. ;;
  43. ;;; Code:
  44. (require 'json)
  45. (eval-when-compile (require 'cl))
  46. (unless (require 'subr-x nil t)
  47. ;; built-in subr-x from 24.4
  48. (defsubst hash-table-keys (hash-table)
  49. "Return a list of keys in HASH-TABLE."
  50. (let ((keys '()))
  51. (maphash (lambda (k _v) (push k keys)) hash-table)
  52. keys)))
  53. (put 'json-reformat-error 'error-message "JSON Reformat error")
  54. (put 'json-reformat-error 'error-conditions '(json-reformat-error error))
  55. (defconst json-reformat:special-chars-as-pretty-string
  56. '((?\" . ?\")
  57. (?\\ . ?\\)))
  58. (defcustom json-reformat:indent-width 4
  59. "How much indentation `json-reformat-region' should do at each level."
  60. :type 'integer
  61. :group 'json-reformat)
  62. (defcustom json-reformat:pretty-string? nil
  63. "Whether to decode the string.
  64. Example:
  65. {\"name\":\"foobar\",\"nick\":\"foo \\u00e4 bar\",\"description\":\"<pre>\\nbaz\\n</pre>\"}
  66. If nil:
  67. {
  68. \"name\": \"foobar\",
  69. \"nick\": \"foo \\u00e4 bar\",
  70. \"description\": \"<pre>\\nbaz\\n<\\/pre>\"
  71. }
  72. Else t:
  73. {
  74. \"name\": \"foobar\",
  75. \"nick\": \"foo ä bar\",
  76. \"description\": \"<pre>
  77. baz
  78. </pre>\"
  79. }"
  80. :type 'boolean
  81. :group 'json-reformat)
  82. (defun json-reformat:indent (level)
  83. (make-string (* level json-reformat:indent-width) ? ))
  84. (defun json-reformat:number-to-string (val)
  85. (number-to-string val))
  86. (defun json-reformat:symbol-to-string (val)
  87. (cond ((equal 't val) "true")
  88. ((equal json-false val) "false")
  89. (t (symbol-name val))))
  90. (defun json-reformat:encode-char-as-pretty (char)
  91. (setq char (encode-char char 'ucs))
  92. (let ((special-char (car (rassoc char json-reformat:special-chars-as-pretty-string))))
  93. (if special-char
  94. (format "\\%c" special-char)
  95. (format "%c" char))))
  96. (defun json-reformat:string-to-string (val)
  97. (if json-reformat:pretty-string?
  98. (format "\"%s\"" (mapconcat 'json-reformat:encode-char-as-pretty val ""))
  99. (json-encode-string val)))
  100. (defun json-reformat:vector-to-string (val level)
  101. (if (= (length val) 0) "[]"
  102. (concat "[\n"
  103. (mapconcat
  104. 'identity
  105. (loop for v across val
  106. collect (concat
  107. (json-reformat:indent (1+ level))
  108. (json-reformat:print-node v (1+ level))
  109. ))
  110. (concat ",\n"))
  111. "\n" (json-reformat:indent level) "]"
  112. )))
  113. (defun json-reformat:print-node (val level)
  114. (cond ((hash-table-p val) (json-reformat:tree-to-string (json-reformat:tree-sibling-to-plist val) level))
  115. ((numberp val) (json-reformat:number-to-string val))
  116. ((vectorp val) (json-reformat:vector-to-string val level))
  117. ((null val) "null")
  118. ((symbolp val) (json-reformat:symbol-to-string val))
  119. (t (json-reformat:string-to-string val))))
  120. (defun json-reformat:tree-sibling-to-plist (root)
  121. (let (pl)
  122. (dolist (key (reverse (hash-table-keys root)) pl)
  123. (setq pl (plist-put pl key (gethash key root))))))
  124. (defun json-reformat:tree-to-string (root level)
  125. (concat "{\n"
  126. (let (key val str)
  127. (while root
  128. (setq key (car root)
  129. val (cadr root)
  130. root (cddr root))
  131. (setq str
  132. (concat str (json-reformat:indent (1+ level))
  133. "\"" key "\""
  134. ": "
  135. (json-reformat:print-node val (1+ level))
  136. (when root ",")
  137. "\n"
  138. )))
  139. str)
  140. (json-reformat:indent level)
  141. "}"))
  142. (defun json-reformat-from-string (string)
  143. (with-temp-buffer
  144. (insert string)
  145. (goto-char (point-min))
  146. (condition-case errvar
  147. (let ((json-key-type 'string)
  148. (json-object-type 'hash-table)
  149. json-tree)
  150. (setq json-tree (json-read))
  151. (json-reformat:print-node json-tree 0))
  152. (json-error
  153. (signal 'json-reformat-error
  154. (list (error-message-string errvar)
  155. (line-number-at-pos (point))
  156. (point)))))))
  157. ;;;###autoload
  158. (defun json-reformat-region (begin end)
  159. "Reformat the JSON in the specified region.
  160. If you want to customize the reformat style,
  161. please see the documentation of `json-reformat:indent-width'
  162. and `json-reformat:pretty-string?'."
  163. (interactive "*r")
  164. (let ((start-line (line-number-at-pos begin))
  165. (start-pos begin))
  166. (save-excursion
  167. (save-restriction
  168. (narrow-to-region begin end)
  169. (goto-char (point-min))
  170. (let (reformatted)
  171. (condition-case errvar
  172. (progn
  173. (setq reformatted
  174. (json-reformat-from-string
  175. (buffer-substring-no-properties (point-min) (point-max))))
  176. (delete-region (point-min) (point-max))
  177. (insert reformatted))
  178. (json-reformat-error
  179. (let ((reason (nth 1 errvar))
  180. (line (nth 2 errvar))
  181. (position (nth 3 errvar)))
  182. (message
  183. "JSON parse error [Reason] %s [Position] In buffer, line %d (char %d)"
  184. reason
  185. (+ start-line line -1)
  186. (+ start-pos position -1))))))))))
  187. (provide 'json-reformat)
  188. ;;; json-reformat.el ends here