├── .github └── workflows │ └── test.yml ├── .gitignore ├── CHANGELOG.md ├── Makefile ├── README.md ├── toml-test.el └── toml.el /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Main workflow 2 | 3 | on: 4 | pull_request: 5 | paths-ignore: 6 | - '**.md' 7 | push: 8 | paths-ignore: 9 | - '**.md' 10 | branches: 11 | - master 12 | 13 | jobs: 14 | test: 15 | runs-on: ubuntu-latest 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | emacs_version: 20 | - '24.5' 21 | - '25.3' 22 | - '26.3' 23 | - '27.2' 24 | - '28.2' 25 | - '29.4' 26 | - '30.1' 27 | - 'snapshot' 28 | include: 29 | - emacs_version: 'snapshot' 30 | allow_failure: true 31 | steps: 32 | - uses: actions/checkout@v2 33 | - uses: purcell/setup-emacs@master 34 | with: 35 | version: ${{ matrix.emacs_version }} 36 | 37 | - name: Run tests 38 | if: matrix.allow_failure != true 39 | run: 'make test' 40 | 41 | - name: Run tests (allow failure) 42 | if: matrix.allow_failure == true 43 | run: 'make test || true' 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.elc 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | emacs-toml's Changelog 2 | ====================== 3 | 4 | Unreleased 5 | ------------------------------ 6 | 7 | ### Other 8 | 9 | - Use Github Actions instead of Travis CI [GH-2] [GH-3] 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | export EMACS ?= emacs 2 | 3 | test: 4 | ${EMACS} -Q --batch \ 5 | --load toml.el \ 6 | --load toml-test.el \ 7 | -f ert-run-tests-batch-and-exit 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | emacs-toml 2 | ========== 3 | 4 | [![Main workflow](https://github.com/gongo/emacs-toml/workflows/Main%20workflow/badge.svg)](https://github.com/gongo/emacs-toml/actions?query=workflow%3A%22Main+workflow%22) 5 | 6 | `toml.el` is a library for parsing TOML (Tom's Obvious, Minimal Language). 7 | 8 | * Learn all about TOML here: https://github.com/mojombo/toml 9 | * Support version: [v0.1.0](https://github.com/mojombo/toml/blob/master/versions/toml-v0.1.0.md) 10 | 11 | ## Example 12 | 13 | Parse the [example.toml](https://github.com/mojombo/toml/blob/master/tests/example.toml) as an example. 14 | 15 | ```lisp 16 | (toml:read-from-string "\ 17 | key1 = \"foo\" 18 | key2 = \"bar\" 19 | key3 = \"333\"") 20 | 21 | ;; => '(("key3" . "333") ("key2" . "bar") ("key1" . "foo")) 22 | ``` 23 | 24 | ```lisp 25 | (toml:read-from-file "example.toml") 26 | 27 | ;; or 28 | 29 | (toml:read-from-string "\ 30 | # This is a TOML document. Boom. 31 | 32 | title = \"TOML Example\" 33 | 34 | \[owner\] 35 | name = \"Tom Preston-Werner\" 36 | organization = \"GitHub\" 37 | bio = \"GitHub Cofounder & CEO\\nLikes tater tots and beer.\" 38 | dob = 1979-05-27T07:32:00Z # First class dates? Why not? 39 | 40 | \[database\] 41 | server = \"192.168.1.1\" 42 | ports = \[ 8001, 8001, 8002 \] 43 | connection_max = 5000 44 | enabled = true 45 | 46 | \[servers\] 47 | 48 | # You can indent as you please. Tabs or spaces. TOML don't care. 49 | \[servers.alpha\] 50 | ip = \"10.0.0.1\" 51 | dc = \"eqdc10\" 52 | 53 | \[servers.beta\] 54 | ip = \"10.0.0.2\" 55 | dc = \"eqdc10\" 56 | 57 | \[clients\] 58 | data = \[ \[\"gamma\", \"delta\"\], \[1, 2\] \] 59 | 60 | # Line breaks are OK when inside arrays 61 | hosts = \[ 62 | \"alpha\", 63 | \"omega\" 64 | \]") 65 | 66 | ;; => '( 67 | ;; ("clients" 68 | ;; ("hosts" "alpha" "omega") 69 | ;; ("data" ("gamma" "delta") (1 2))) 70 | ;; ("servers" 71 | ;; ("beta" ("dc" . "eqdc10") ("ip" . "10.0.0.2")) 72 | ;; ("alpha" ("dc" . "eqdc10") ("ip" . "10.0.0.1"))) 73 | ;; ("database" 74 | ;; ("enabled" . t) 75 | ;; ("connection_max" . 5000) 76 | ;; ("ports" 8001 8001 8002) 77 | ;; ("server" . "192.168.1.1")) 78 | ;; ("owner" 79 | ;; ("dob" 0 32 7 27 5 1979) 80 | ;; ("bio" . "GitHub Cofounder & CEO\\nLikes tater tots and beer.") 81 | ;; ("organization" . "GitHub") 82 | ;; ("name" . "Tom Preston-Werner")) 83 | ;; ("title" . "TOML Example")) 84 | ``` 85 | 86 | ## Spec 87 | 88 | In `emacs-toml`, "key groups" and "key" key pattern are as follows: 89 | 90 | * `key` = `[a-zA-Z][a-zA-Z0-9_]*` 91 | * `keygroup` = `[a-zA-Z][a-zA-Z0-9_\\.]*` 92 | * The end doesn't end in the period. 93 | 94 | ## Test 95 | 96 | Use [Cask.el](https://github.com/rejeep/cask.el). follow commands: 97 | 98 | ``` 99 | $ make test 100 | cask exec emacs -Q --batch \ 101 | --load toml.el \ 102 | --load toml-test.el \ 103 | -f ert-run-tests-batch-and-exit 104 | Real cl-lib shadowed by compatibility cl-lib? (/Users/gongo/.emacs.d/elpa/cl-lib-0.3/cl-lib.elc) 105 | Real cl-lib shadowed by compatibility cl-lib? (/Users/gongo/.emacs.d/elpa/cl-lib-0.3/cl-lib.elc) 106 | Running 21 tests (2013-08-29 22:33:46+0900) 107 | passed 1/21 toml-test-error:parse 108 | passed 2/21 toml-test-error:read-boolean 109 | passed 3/21 toml-test-error:read-datetime 110 | passed 4/21 toml-test-error:read-escaped-char 111 | passed 5/21 toml-test-error:read-key 112 | passed 6/21 toml-test-error:read-keygroup 113 | passed 7/21 toml-test-error:read-numeric 114 | passed 8/21 toml-test-error:read-string 115 | passed 9/21 toml-test:make-hashes 116 | Mark set 117 | Mark set 118 | Mark set 119 | Mark set 120 | Mark set 121 | passed 10/21 toml-test:parse 122 | passed 11/21 toml-test:read-char 123 | passed 12/21 toml-test:read-char-with-char-p 124 | passed 13/21 toml-test:read-datetime 125 | passed 14/21 toml-test:read-escaped-char 126 | passed 15/21 toml-test:read-key 127 | passed 16/21 toml-test:read-keygroup 128 | passed 17/21 toml-test:read-numeric 129 | passed 18/21 toml-test:read-string 130 | passed 19/21 toml-test:seek-beginning-of-next-line 131 | passed 20/21 toml-test:seek-non-whitespace 132 | passed 21/21 toml-test:seek-readable-point 133 | 134 | Ran 21 tests, 21 results as expected (2013-08-29 22:33:46+0900) 135 | ``` 136 | 137 | ## License 138 | 139 | MIT License. see `toml.el`. 140 | -------------------------------------------------------------------------------- /toml-test.el: -------------------------------------------------------------------------------- 1 | (require 'ert) 2 | (require 'toml) 3 | 4 | (defmacro toml-test:buffer-setup (string &rest body) 5 | `(with-temp-buffer 6 | (insert ,string) 7 | (goto-char (point-min)) 8 | ,@body)) 9 | 10 | (ert-deftest toml-test:seek-beginning-of-next-line () 11 | (toml-test:buffer-setup 12 | "\12345\n67890\nabcde" 13 | 14 | (toml:seek-beginning-of-next-line) 15 | (should (eq (char-after (point)) ?6)) 16 | 17 | (forward-char 3) ;; "8" on the second line 18 | (toml:seek-beginning-of-next-line) 19 | (should (eq (toml:get-char-at-point) ?a)))) 20 | 21 | (ert-deftest toml-test:seek-readable-point () 22 | (toml-test:buffer-setup 23 | "\ 24 | 25 | # comment line 26 | # comment line 2 # 3 27 | aiueo" 28 | 29 | (toml:seek-readable-point) 30 | (should (eq (char-after (point)) ?a)))) 31 | 32 | (ert-deftest toml-test:seek-non-whitespace () 33 | (toml-test:buffer-setup 34 | " hello\n\t \tworld" 35 | 36 | (toml:seek-non-whitespace) 37 | (should (eq (char-after (point)) ?h)) 38 | 39 | (toml:seek-beginning-of-next-line) 40 | (toml:seek-non-whitespace) 41 | (should (eq (toml:get-char-at-point) ?w)))) 42 | 43 | (ert-deftest toml-test:read-char () 44 | (toml-test:buffer-setup 45 | "aiueo" 46 | (should (equal "a" (toml:read-char))) 47 | (should (equal ?i (toml:get-char-at-point))) 48 | 49 | (should (equal "i" (toml:read-char))) 50 | (should (equal ?u (toml:get-char-at-point))))) 51 | 52 | (ert-deftest toml-test:read-char-with-char-p () 53 | (toml-test:buffer-setup 54 | "aiueo" 55 | (should (equal ?a (toml:read-char t))) 56 | (should (equal ?i (toml:get-char-at-point))) 57 | 58 | (should (equal ?i (toml:read-char t))) 59 | (should (equal ?u (toml:get-char-at-point))))) 60 | 61 | (ert-deftest toml-test:read-escaped-char () 62 | (dolist (char '("\\b" "\\t" "\\n" "\\f" "\\r" "\\\"" "\\\/" "\\\\" "\\u1234")) 63 | (toml-test:buffer-setup 64 | char 65 | (should (equal char (toml:read-escaped-char))) 66 | (should (toml:end-of-line-p))))) 67 | 68 | (ert-deftest toml-test-error:read-escaped-char () 69 | (dolist (char '(" " " \\b" "a" "\\a" "\\c" "\\uABC!" "\\u____")) 70 | (toml-test:buffer-setup 71 | char 72 | (should-error (toml:read-escaped-char) :type 'toml-string-escape-error)))) 73 | 74 | (ert-deftest toml-test:read-string () 75 | (toml-test:buffer-setup 76 | "\"GitHub Cofounder & CEO\\nLikes tater tots and beer.\"" 77 | (should (equal "GitHub Cofounder & CEO\\nLikes tater tots and beer." (toml:read-string))) 78 | (should (toml:end-of-line-p)))) 79 | 80 | (ert-deftest toml-test-error:read-string () 81 | (dolist (str '("aiueo" ;; Not start with '"' 82 | "\"hogehoge" ;; Not end with '"' 83 | " \"aiueo\"" ;; Not start with '"' 84 | )) 85 | (toml-test:buffer-setup 86 | str 87 | (should-error (toml:read-string) :type 'toml-string-error)))) 88 | 89 | (ert-deftest toml-test:read-boolean () 90 | (toml-test:buffer-setup 91 | "true" 92 | (should (equal t (toml:read-boolean))) 93 | (should (toml:end-of-line-p))) 94 | 95 | (toml-test:buffer-setup 96 | "false" 97 | (should (null (toml:read-boolean))) 98 | (should (toml:end-of-line-p)))) 99 | 100 | (ert-deftest toml-test-error:read-boolean () 101 | (toml-test:buffer-setup 102 | "truu" 103 | (should-error (toml:read-boolean) :type 'toml-boolean-error)) 104 | 105 | (toml-test:buffer-setup 106 | " false" 107 | (should-error (toml:read-boolean) :type 'toml-boolean-error))) 108 | 109 | (ert-deftest toml-test:read-datetime () 110 | (toml-test:buffer-setup 111 | "1979-05-27T07:32:00Z" 112 | (should (equal '(0 32 7 27 5 1979) (toml:read-datetime))) 113 | (should (toml:end-of-line-p)))) 114 | 115 | (ert-deftest toml-test-error:read-datetime () 116 | (dolist (str '("1979-05-27" "1979-35-27T07:32:00Z" " 1979-05-27T07:32:00Z")) 117 | (toml-test:buffer-setup 118 | str 119 | (should-error (toml:read-datetime) :type 'toml-datetime-error)))) 120 | 121 | (ert-deftest toml-test:read-numeric () 122 | (toml-test:buffer-setup 123 | "1" 124 | (let ((numeric (toml:read-numeric))) 125 | (should (toml:end-of-line-p)) 126 | (should (equal 1 numeric)) 127 | (should (integerp numeric)) 128 | (should (wholenump numeric)))) 129 | 130 | (toml-test:buffer-setup 131 | "42" 132 | (let ((numeric (toml:read-numeric))) 133 | (should (toml:end-of-line-p)) 134 | (should (equal 42 numeric)) 135 | (should (integerp numeric)) 136 | (should (wholenump numeric)))) 137 | 138 | (toml-test:buffer-setup 139 | "-17" 140 | (let ((numeric (toml:read-numeric))) 141 | (should (toml:end-of-line-p)) 142 | (should (equal -17 numeric)) 143 | (should (integerp numeric)) 144 | (should (not (wholenump numeric))))) 145 | 146 | (toml-test:buffer-setup 147 | "3.1415" 148 | (let ((numeric (toml:read-numeric))) 149 | (should (toml:end-of-line-p)) 150 | (should (equal 3.1415 numeric)) 151 | (should (floatp numeric)))) 152 | 153 | (toml-test:buffer-setup 154 | "-0.01" 155 | (let ((numeric (toml:read-numeric))) 156 | (should (toml:end-of-line-p)) 157 | (should (equal -0.01 numeric)) 158 | (should (floatp numeric))))) 159 | 160 | (ert-deftest toml-test-error:read-numeric () 161 | (dolist (str '("" "+11" "- 1.1" " 1.1" ".1" "1.1.1" "1.1.1.1")) 162 | (toml-test:buffer-setup 163 | str 164 | (should-error (toml:read-numeric) :type 'toml-numeric-error)))) 165 | 166 | (ert-deftest toml-test:read-key () 167 | (toml-test:buffer-setup 168 | "a = 3" 169 | (should (equal "a" (toml:read-key))) 170 | (should (eq ?3 (toml:get-char-at-point)))) 171 | 172 | (toml-test:buffer-setup 173 | "biueo = true" 174 | (should (equal "biueo" (toml:read-key))) 175 | (should (eq ?t (toml:get-char-at-point)))) 176 | 177 | (toml-test:buffer-setup 178 | "connection_max = 5000" 179 | (should (equal "connection_max" (toml:read-key))) 180 | (should (eq ?5 (toml:get-char-at-point)))) 181 | 182 | (toml-test:buffer-setup 183 | "connection-max = name" 184 | (should (equal "connection-max" (toml:read-key))) 185 | (should (eq ?n (toml:get-char-at-point)))) 186 | 187 | (toml-test:buffer-setup 188 | "server12 = name" 189 | (should (equal "server12" (toml:read-key))) 190 | (should (eq ?n (toml:get-char-at-point))))) 191 | 192 | (ert-deftest toml-test-error:read-key () 193 | ;; no key 194 | (toml-test:buffer-setup 195 | " = 3" 196 | (should-error (toml:read-key) :type 'toml-key-error)) 197 | 198 | ;; key only 199 | (toml-test:buffer-setup 200 | "key" 201 | (should-error (toml:read-key) :type 'toml-key-error)) 202 | 203 | ;; start with number. 204 | (toml-test:buffer-setup 205 | "1biueo = true" 206 | (should-error (toml:read-key) :type 'toml-key-error)) 207 | 208 | ;; end with underscore 209 | (toml-test:buffer-setup 210 | "connection_ = 5000" 211 | (should-error (toml:read-key) :type 'toml-key-error))) 212 | 213 | 214 | (ert-deftest toml-test:read-keygroup () 215 | (toml-test:buffer-setup 216 | "[aiueo]" 217 | (should (equal '("aiueo") (toml:read-keygroup)))) 218 | 219 | (toml-test:buffer-setup 220 | "[ai-ueo]" 221 | (should (equal '("ai-ueo") (toml:read-keygroup)))) 222 | 223 | (toml-test:buffer-setup 224 | "[servers] 225 | [servers.alpha] 226 | 227 | key = value" 228 | (should (equal '("servers" "alpha") (toml:read-keygroup))) 229 | (should (eq ?k (toml:get-char-at-point)))) 230 | 231 | (toml-test:buffer-setup 232 | "[servers] 233 | [servers.alpha] 234 | [client]" 235 | (should (equal '("client") (toml:read-keygroup))))) 236 | 237 | (ert-deftest toml-test-error:read-keygroup () 238 | (toml-test:buffer-setup 239 | "[]" 240 | (should-error (toml:read-keygroup) :type 'toml-keygroup-error)) 241 | 242 | ;; end with underscore "_" 243 | (toml-test:buffer-setup 244 | "[foo.bar_]" 245 | (should-error (toml:read-keygroup) :type 'toml-keygroup-error)) 246 | 247 | ;; end with period "." 248 | (toml-test:buffer-setup 249 | "[foo.bar.]" 250 | (should-error (toml:read-keygroup) :type 'toml-keygroup-error))) 251 | 252 | (ert-deftest toml-test:make-hashes () 253 | (let (hash) 254 | (setq hash (toml:make-hashes '("servers" "alpha") "ip" "192.0.2.1" hash)) 255 | (should (equal '("ip" . "192.0.2.1") (toml:assoc '("servers" "alpha" "ip") hash))) 256 | 257 | (setq hash (toml:make-hashes '("servers" "alpha") "dc" "eqdc10" hash)) 258 | (should (equal '("ip" . "192.0.2.1") (toml:assoc '("servers" "alpha" "ip") hash))) 259 | (should (equal '("dc" . "eqdc10") (toml:assoc '("servers" "alpha" "dc") hash))) 260 | 261 | (setq hash (toml:make-hashes '("servers" "beta") "dc" "eqdc10" hash)) 262 | (should (equal '("ip" . "192.0.2.1") (toml:assoc '("servers" "alpha" "ip") hash))) 263 | (should (equal '("dc" . "eqdc10") (toml:assoc '("servers" "alpha" "dc") hash))) 264 | (should (equal '("dc" . "eqdc10") (toml:assoc '("servers" "beta" "dc") hash))) 265 | 266 | (setq hash (toml:make-hashes '("client") "ip" "192.0.2.123" hash)) 267 | (should (equal '("ip" . "192.0.2.1") (toml:assoc '("servers" "alpha" "ip") hash))) 268 | (should (equal '("dc" . "eqdc10") (toml:assoc '("servers" "alpha" "dc") hash))) 269 | (should (equal '("dc" . "eqdc10") (toml:assoc '("servers" "beta" "dc") hash))) 270 | (should (equal '("ip" . "192.0.2.123") (toml:assoc '("client" "ip") hash))) 271 | 272 | ;; update 273 | (setq hash (toml:make-hashes '("servers" "alpha") "ip" "192.0.2.233" hash)) 274 | (should (equal '("ip" . "192.0.2.233") (toml:assoc '("servers" "alpha" "ip") hash))))) 275 | 276 | (ert-deftest toml-test:parse() 277 | (toml-test:buffer-setup 278 | "\ 279 | \[a.b\] 280 | c = 1 281 | g = { bar = 4711, baz = \"foo\" } 282 | 283 | \[a\] 284 | d = 2 285 | e = [] 286 | f = { foo = 2342 } 287 | " 288 | (let ((parsed (toml:read))) 289 | (should (equal '("c" . 1) (toml:assoc '("a" "b" "c") parsed))) 290 | (should (equal '("d" . 2) (toml:assoc '("a" "d") parsed))) 291 | (should (equal '("e" . nil) (toml:assoc '("a" "e") parsed))) 292 | (should (equal '("f" . (("foo" . 2342))) (toml:assoc '("a" "f") parsed))) 293 | (should (equal '("g" ("bar" . 4711) ("baz" . "foo")) (toml:assoc '("a" "b" "g") parsed))) 294 | )) 295 | 296 | (toml-test:buffer-setup 297 | "\ 298 | # This is a TOML document. Boom. 299 | 300 | title = \"TOML Example\" 301 | 302 | \[owner\] 303 | name = \"Tom Preston-Werner\" 304 | organization = \"GitHub\" 305 | bio = \"GitHub Cofounder & CEO\\nLikes tater tots and beer.\" 306 | dob = 1979-05-27T07:32:00Z # First class dates? Why not? 307 | 308 | \[database\] 309 | server = \"192.168.1.1\" 310 | ports = \[ 8001, 8001, 8002 \] 311 | connection_max = 5000 312 | enabled = true 313 | 314 | \[servers\] 315 | 316 | # You can indent as you please. Tabs or spaces. TOML don't care. 317 | \[servers.alpha\] 318 | ip = \"10.0.0.1\" 319 | dc = \"eqdc10\" 320 | 321 | \[servers.beta\] 322 | ip = \"10.0.0.2\" 323 | dc = \"eqdc10\" 324 | 325 | \[clients\] 326 | data = \[ \[\"gamma\", \"delta\"\], \[1, 2\] \] 327 | 328 | # Line breaks are OK when inside arrays 329 | hosts = \[ 330 | \"alpha\", 331 | \"omega\" 332 | \]" 333 | (let ((parsed (toml:read))) 334 | (should (toml:assoc '("servers" "beta" "dc") parsed)) 335 | (should (toml:assoc '("clients" "data") parsed)) 336 | (should (toml:assoc '("database" "ports") parsed)) 337 | ))) 338 | 339 | 340 | (ert-deftest toml-test-error:parse () 341 | (toml-test:buffer-setup 342 | "\ 343 | \[a\] 344 | b = 1 345 | 346 | \[a\] 347 | c = 2" 348 | (should-error (toml:read) :type 'toml-redefine-keygroup-error)) 349 | 350 | (toml-test:buffer-setup 351 | "\ 352 | \[a\] 353 | b = 1 354 | 355 | \[a.b\] 356 | c = 2" 357 | (should-error (toml:read) :type 'toml-redefine-key-error)) 358 | ) 359 | -------------------------------------------------------------------------------- /toml.el: -------------------------------------------------------------------------------- 1 | ;;; toml.el --- TOML (Tom's Obvious, Minimal Language) parser 2 | 3 | ;; Copyright (C) 2013 by Wataru MIYAGUNI 4 | 5 | ;; Author: Wataru MIYAGUNI 6 | ;; URL: https://github.com/gongo/emacs-toml 7 | ;; Keywords: toml parser 8 | ;; Version: 0.0.1 9 | 10 | ;; MIT License 11 | ;; 12 | ;; Permission is hereby granted, free of charge, to any person obtaining a copy 13 | ;; of this software and associated documentation files (the "Software"), to deal 14 | ;; in the Software without restriction, including without limitation the rights 15 | ;; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | ;; copies of the Software, and to permit persons to whom the Software is 17 | ;; furnished to do so, subject to the following conditions: 18 | ;; 19 | ;; The above copyright notice and this permission notice shall be included in 20 | ;; all copies or substantial portions of the Software. 21 | ;; 22 | ;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | ;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | ;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | ;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | ;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | ;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 28 | ;; THE SOFTWARE. 29 | 30 | ;;; Commentary: 31 | 32 | ;; This is a library for parsing TOML (Tom's Obvious, Minimal 33 | ;; Language). 34 | 35 | ;; Learn all about TOML here: https://github.com/mojombo/toml 36 | 37 | ;; Inspired by json.el. thanks!! 38 | 39 | ;;; Code: 40 | 41 | (require 'parse-time) 42 | 43 | (defconst toml->special-escape-characters 44 | '(?b ?t ?n ?f ?r ?\" ?\/ ?\\) 45 | "Characters which are escaped in TOML. 46 | 47 | \\b - backspace (U+0008) 48 | \\t - tab (U+0009) 49 | \\n - linefeed (U+000A) 50 | \\f - form feed (U+000C) 51 | \\r - carriage return (U+000D) 52 | \\\" - quote (U+0022) 53 | \\\/ - slash (U+002F) 54 | \\\\ - backslash (U+005C) 55 | 56 | notes: 57 | 58 | Excluded four hex (\\uXXXX). Do check in `toml:read-escaped-char'") 59 | 60 | (defconst toml->read-table 61 | (let ((table 62 | '((?t . toml:read-boolean) 63 | (?f . toml:read-boolean) 64 | (?\[ . toml:read-array) 65 | (?{ . toml:read-inline-table) 66 | (?\" . toml:read-string)))) 67 | (mapc (lambda (char) 68 | (push (cons char 'toml:read-start-with-number) table)) 69 | '(?- ?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9)) 70 | table)) 71 | 72 | (defconst toml->regexp-datetime 73 | "\ 74 | \\([0-9]\\{4\\}\\)-\ 75 | \\(0[1-9]\\|1[0-2]\\)-\ 76 | \\(0[1-9]\\|[1-2][0-9]\\|3[0-1]\\)T\ 77 | \\([0-1][0-9]\\|2[0-4]\\):\ 78 | \\([0-5][0-9]\\):\ 79 | \\([0-5][0-9]\\)Z" 80 | "Regular expression for a datetime (Zulu time format).") 81 | 82 | (defconst toml->regexp-numeric 83 | "\\(-?[0-9]+[\\.0-9\\]*\\)" 84 | "Regular expression for a numeric.") 85 | 86 | ;; Error conditions 87 | 88 | (put 'toml-error 'error-message "Unknown TOML error") 89 | (put 'toml-error 'error-conditions '(toml-error error)) 90 | 91 | (put 'toml-string-error 'error-message "Bad string") 92 | (put 'toml-string-error 'error-conditions 93 | '(toml-string-error toml-error error)) 94 | 95 | (put 'toml-string-escape-error 'error-message "Bad escaped string") 96 | (put 'toml-string-escape-error 'error-conditions 97 | '(toml-string-escape-error toml-string-error toml-error error)) 98 | 99 | (put 'toml-string-unicode-escape-error 'error-message "Bad unicode escaped string") 100 | (put 'toml-string-unicode-escape-error 'error-conditions 101 | '(toml-string-unicode-escape-error 102 | toml-string-escape-error 103 | toml-string-error toml-error error)) 104 | 105 | (put 'toml-boolean-error 'error-message "Bad boolean") 106 | (put 'toml-boolean-error 'error-conditions 107 | '(toml-boolean-error toml-error error)) 108 | 109 | (put 'toml-datetime-error 'error-message "Bad datetime") 110 | (put 'toml-datetime-error 'error-conditions 111 | '(toml-datetime-error toml-error error)) 112 | 113 | (put 'toml-numeric-error 'error-message "Bad numeric") 114 | (put 'toml-numeric-error 'error-conditions 115 | '(toml-numeric-error toml-error error)) 116 | 117 | (put 'toml-start-with-number-error 'error-message "Bad start-with-number") 118 | (put 'toml-start-with-number-error 'error-conditions 119 | '(toml-start-with-number-error toml-error error)) 120 | 121 | (put 'toml-array-error 'error-message "Bad array") 122 | (put 'toml-array-error 'error-conditions 123 | '(toml-array-error toml-error error)) 124 | 125 | (put 'toml-key-error 'error-message "Bad key") 126 | (put 'toml-key-error 'error-conditions 127 | '(toml-key-error toml-error error)) 128 | 129 | (put 'toml-keygroup-error 'error-message "Bad keygroup") 130 | (put 'toml-keygroup-error 'error-conditions 131 | '(toml-keygroup-error toml-error error)) 132 | 133 | (put 'toml-value-error 'error-message "Bad readable value") 134 | (put 'toml-value-error 'error-conditions 135 | '(toml-value-error toml-error error)) 136 | 137 | (put 'toml-redefine-keygroup-error 'error-message "Redefine keygroup error") 138 | (put 'toml-redefine-keygroup-error 'error-conditions 139 | '(toml-redefine-keygroup-error toml-error error)) 140 | 141 | (put 'toml-redefine-key-error 'error-message "Redefine key error") 142 | (put 'toml-redefine-key-error 'error-conditions 143 | '(toml-redefine-key-error toml-error error)) 144 | 145 | (defun toml:assoc (keys hash) 146 | "Example: 147 | 148 | (toml:assoc '(\"servers\" \"alpha\" \"ip\") hash)" 149 | (let (element) 150 | (catch 'break 151 | (dolist (k keys) 152 | (unless (toml:alistp hash) (throw 'break nil)) 153 | (setq element (assoc k hash)) 154 | (if element 155 | (setq hash (cdr element)) 156 | (throw 'break nil))) 157 | element))) 158 | 159 | (defun toml:alistp (alist) 160 | "Return t if ALIST is a list of association lists, nil otherwise." 161 | (when (listp alist) 162 | (catch 'break 163 | (dolist (al alist) 164 | (unless (consp al) (throw 'break nil))) 165 | t))) 166 | 167 | (defun toml:end-of-line-p () 168 | (looking-at "$")) 169 | 170 | (defun toml:get-char-at-point () 171 | (char-after (point))) 172 | 173 | (defun toml:seek-beginning-of-next-line () 174 | "Move point to beginning of next line." 175 | (forward-line) 176 | (beginning-of-line)) 177 | 178 | (defun toml:seek-readable-point () 179 | "Move point forward, stopping readable point. (toml->read-table). 180 | 181 | Skip target: 182 | 183 | - whitespace (Tab or Space) 184 | - comment line (start with hash symbol)" 185 | (toml:seek-non-whitespace) 186 | (while (and (not (eobp)) 187 | (char-equal (toml:get-char-at-point) ?#)) 188 | (end-of-line) 189 | (unless (eobp) 190 | (toml:seek-beginning-of-next-line) 191 | (toml:seek-non-whitespace)))) 192 | 193 | (defun toml:seek-non-whitespace () 194 | "Move point forward, stopping before a char end-of-buffer or not in whitespace (tab and space)." 195 | (if (re-search-forward "[^ \t\n]" nil t) 196 | (backward-char) 197 | (re-search-forward "[ \t\n]+\\'" nil t))) 198 | 199 | (defun toml:search-forward (regexp) 200 | "Search forward from point for regular expression REGEXP. 201 | Move point to the end of the occurrence found, and return point." 202 | (when (looking-at regexp) 203 | (forward-char (length (match-string-no-properties 0))) 204 | t)) 205 | 206 | (defun toml:read-char (&optional char-p) 207 | "Read character at point. Set point to next point. 208 | If CHAR-P is nil, return character as string, 209 | and not nil, return character as char. 210 | 211 | Move point a character forward." 212 | (let ((char (toml:get-char-at-point))) 213 | (forward-char) 214 | (if char-p char 215 | (char-to-string char)))) 216 | 217 | (defun toml:read-escaped-char () 218 | "Read escaped character at point. Return character as string. 219 | Move point to the end of read characters." 220 | (unless (eq ?\\ (toml:read-char t)) 221 | (signal 'toml-string-escape-error (list (point)))) 222 | (let* ((char (toml:read-char t)) 223 | (special (memq char toml->special-escape-characters))) 224 | (cond 225 | (special (concat (list ?\\ char))) 226 | ((and (eq char ?u) 227 | (toml:search-forward "[0-9A-Fa-f]\\{4\\}")) 228 | (concat "\\u" (match-string 0))) 229 | (t (signal 'toml-string-unicode-escape-error (list (point))))))) 230 | 231 | (defun toml:read-string () 232 | "Read string at point that surrounded by double quotation mark. 233 | Move point to the end of read strings." 234 | (unless (eq ?\" (toml:get-char-at-point)) 235 | (signal 'toml-string-error (list (point)))) 236 | (let ((characters '()) 237 | (char (toml:read-char))) 238 | (while (not (eq char ?\")) 239 | (when (toml:end-of-line-p) 240 | (signal 'toml-string-error (list (point)))) 241 | (push (if (eq char ?\\) 242 | (toml:read-escaped-char) 243 | (toml:read-char)) 244 | characters) 245 | (setq char (toml:get-char-at-point))) 246 | (forward-char) 247 | (apply 'concat (nreverse characters)))) 248 | 249 | (defun toml:read-boolean () 250 | "Read boolean at point. Return t or nil. 251 | Move point to the end of read boolean string." 252 | (cond 253 | ((toml:search-forward "true") t) 254 | ((toml:search-forward "false") nil) 255 | (t 256 | (signal 'toml-boolean-error (list (point)))))) 257 | 258 | (defun toml:read-datetime () 259 | "Read datetime at point. 260 | Return time list (seconds, minutes, hour, day, month and year). 261 | Move point to the end of read datetime string." 262 | (unless (toml:search-forward toml->regexp-datetime) 263 | (signal 'toml-datetime-error (list (point)))) 264 | (let ((seconds (string-to-number (match-string-no-properties 6))) 265 | (minutes (string-to-number (match-string-no-properties 5))) 266 | (hour (string-to-number (match-string-no-properties 4))) 267 | (day (string-to-number (match-string-no-properties 3))) 268 | (month (string-to-number (match-string-no-properties 2))) 269 | (year (string-to-number (match-string-no-properties 1)))) 270 | (list seconds minutes hour day month year))) 271 | 272 | (defun toml:read-numeric () 273 | "Read numeric (integer or float) at point. Return numeric. 274 | Move point to the end of read numeric string." 275 | (unless (toml:search-forward toml->regexp-numeric) 276 | (signal 'toml-numeric-error (list (point)))) 277 | (let ((numeric (match-string-no-properties 0))) 278 | (if (with-temp-buffer 279 | (insert numeric) 280 | (goto-char (point-min)) 281 | (search-forward "." nil t 2)) ;; e.g. "0.21.1" is NG 282 | (signal 'toml-numeric-error (list (point))) 283 | (string-to-number numeric)))) 284 | 285 | (defun toml:read-start-with-number () 286 | "Read string that start with number at point. 287 | Move point to the end of read string." 288 | (cond 289 | ((looking-at toml->regexp-datetime) (toml:read-datetime)) 290 | ((looking-at toml->regexp-numeric) (toml:read-numeric)) 291 | (t 292 | (signal 'toml-start-with-number-error (list (point)))))) 293 | 294 | (defun toml:read-array () 295 | (unless (eq ?\[ (toml:get-char-at-point)) 296 | (signal 'toml-array-error (list (point)))) 297 | (mark-sexp) 298 | (forward-char) 299 | (let (elements char-after-read) 300 | (while (not (char-equal (toml:get-char-at-point) ?\])) 301 | (push (toml:read-value) elements) 302 | (toml:seek-readable-point) 303 | (setq char-after-read (toml:get-char-at-point)) 304 | (unless (char-equal char-after-read ?\]) 305 | (if (char-equal char-after-read ?,) 306 | (progn 307 | (forward-char) 308 | (toml:seek-readable-point)) 309 | (signal 'toml-array-error (list (point)))))) 310 | (forward-char) 311 | (nreverse elements))) 312 | 313 | (defun toml:read-inline-table () 314 | (unless (eq ?{ (toml:get-char-at-point)) 315 | (signal 'toml-inline-table-error (list (point)))) 316 | (forward-char) 317 | (let (elements char-after-read) 318 | (while (not (char-equal (toml:get-char-at-point) ?})) 319 | (let ((key (toml:read-key)) 320 | (value (toml:read-value))) 321 | (push `(,key . ,value) elements)) 322 | (toml:seek-readable-point) 323 | (setq char-after-read (toml:get-char-at-point)) 324 | (unless (char-equal char-after-read ?}) 325 | (if (char-equal char-after-read ?,) 326 | (progn 327 | (forward-char) 328 | (toml:seek-readable-point)) 329 | (signal 'toml-array-error (list (point)))))) 330 | (forward-char) 331 | (nreverse elements))) 332 | 333 | (defun toml:read-value () 334 | (toml:seek-readable-point) 335 | (if (eobp) nil 336 | (let ((read-function (cdr (assq (toml:get-char-at-point) toml->read-table)))) 337 | (if (functionp read-function) 338 | (funcall read-function) 339 | (signal 'toml-value-error (list (point))))))) 340 | 341 | (defun toml:read-keygroup () 342 | (toml:seek-readable-point) 343 | (let (keygroup) 344 | (while (and (not (eobp)) 345 | (char-equal (toml:get-char-at-point) ?\[)) 346 | (if (toml:search-forward "\\[\\([a-zA-Z][a-zA-Z0-9_\\.-]*\\)\\]") 347 | (let ((keygroup-string (match-string-no-properties 1))) 348 | (when (string-match "\\(_\\|\\.\\)\\'" keygroup-string) 349 | (signal 'toml-keygroup-error (list (point)))) 350 | (setq keygroup (split-string (match-string-no-properties 1) "\\."))) 351 | (signal 'toml-keygroup-error (list (point)))) 352 | (toml:seek-readable-point)) 353 | keygroup)) 354 | 355 | (defun toml:read-key () 356 | (toml:seek-readable-point) 357 | (if (eobp) nil 358 | (if (toml:search-forward "\\([a-zA-Z][a-zA-Z0-9_-]*\\) *= *") 359 | (let ((key (match-string-no-properties 1))) 360 | (when (string-match "_\\'" key) 361 | (signal 'toml-key-error (list (point)))) 362 | key) 363 | (signal 'toml-key-error (list (point)))))) 364 | 365 | (defun toml:make-hashes (keygroup key value hashes) 366 | (let ((keys (append keygroup (list key)))) 367 | (toml:make-hashes-of-alist hashes keys value))) 368 | 369 | (defun toml:make-hashes-of-alist (hashes keys value) 370 | (let* ((key (car keys)) 371 | (descendants (cdr keys)) 372 | (element (assoc key hashes)) 373 | (children (cdr element))) 374 | (setq hashes (delete element hashes)) 375 | (if descendants 376 | (progn 377 | (setq element (toml:make-hashes-of-alist children descendants value)) 378 | (add-to-list 'hashes (cons key element))) 379 | (progn 380 | (add-to-list 'hashes (cons key value)))))) 381 | 382 | (defun toml:read () 383 | "Parse and return the TOML object following point." 384 | (let (current-keygroup 385 | current-key 386 | current-value 387 | hashes keygroup-history) 388 | (while (not (eobp)) 389 | (toml:seek-readable-point) 390 | 391 | ;; Check re-define keygroup 392 | (let ((keygroup (toml:read-keygroup))) 393 | (when keygroup 394 | (if (and (not (eq keygroup current-keygroup)) 395 | (member keygroup keygroup-history)) 396 | (signal 'toml-redefine-keygroup-error (list (point))) 397 | (setq current-keygroup keygroup)))) 398 | (add-to-list 'keygroup-history current-keygroup) 399 | 400 | (let ((elm (toml:assoc current-keygroup hashes))) 401 | (when (and elm (not (toml:alistp (cdr elm)))) 402 | (signal 'toml-redefine-key-error (list (point))))) 403 | 404 | ;; Check re-define key (with keygroup) 405 | (setq current-key (toml:read-key)) 406 | (when (toml:assoc (append current-keygroup (list current-key)) hashes) 407 | (signal 'toml-redefine-key-error (list (point)))) 408 | 409 | (setq current-value (toml:read-value)) 410 | (setq hashes (toml:make-hashes current-keygroup 411 | current-key 412 | current-value 413 | hashes)) 414 | 415 | (toml:seek-readable-point)) 416 | hashes)) 417 | 418 | (defun toml:read-from-string (string) 419 | "Read the TOML object contained in STRING and return it." 420 | (with-temp-buffer 421 | (insert string) 422 | (goto-char (point-min)) 423 | (toml:read))) 424 | 425 | (defun toml:read-from-file (file) 426 | "Read the TOML object contained in FILE and return it." 427 | (with-temp-buffer 428 | (insert-file-contents file) 429 | (goto-char (point-min)) 430 | (toml:read))) 431 | 432 | (provide 'toml) 433 | 434 | ;;; toml.el ends here 435 | --------------------------------------------------------------------------------