├── .gitignore ├── go.mod ├── jsonfmt.go ├── jsonfmt └── cmd.go ├── jsonfmt_test.go ├── readme.md ├── testdata ├── inp_comment_block.json ├── inp_comment_block_nested.json ├── inp_lines.json ├── inp_long_comments.json ├── inp_long_pure.json ├── inp_short_comments.json ├── inp_short_nopunc.json ├── inp_short_pure.json ├── out_comment_block_multi_line.json ├── out_comment_block_nested_multi_line.json ├── out_lines.json ├── out_long_hybrid_commas.json ├── out_long_hybrid_commas_comments.json ├── out_long_multi.json ├── out_long_single_comments.json ├── out_long_single_stripped.json └── out_short_punc.json └── unlicense /.gitignore: -------------------------------------------------------------------------------- 1 | /* 2 | !/.gitignore 3 | !/*.go 4 | !/go.mod 5 | !/go.sum 6 | !/readme.md 7 | !/unlicense 8 | !/jsonfmt 9 | !/testdata 10 | /testdata/*_fmted.* 11 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mitranim/jsonfmt 2 | 3 | go 1.20 4 | -------------------------------------------------------------------------------- /jsonfmt.go: -------------------------------------------------------------------------------- 1 | /* 2 | Flexible JSON formatter. Features: 3 | 4 | - Preserves order. 5 | - Fits dicts and lists on a single line until a certain width (configurable). 6 | - Supports comments (configurable). 7 | - Supports trailing commas (configurable). 8 | - Fixes missing or broken punctuation. 9 | - Tiny Go library + optional tiny CLI. 10 | 11 | Current limitations: 12 | 13 | - Always permissive. Unrecognized non-whitespace is treated as arbitrary 14 | content on par with strings, numbers, etc. 15 | - Slower than `json.Indent` from the Go standard library. 16 | - Input must be UTF-8. 17 | 18 | Source and readme: https://github.com/mitranim/jsonfmt. 19 | */ 20 | package jsonfmt 21 | 22 | import ( 23 | "bytes" 24 | "encoding/json" 25 | "strings" 26 | "unicode/utf8" 27 | "unsafe" 28 | ) 29 | 30 | /* 31 | Default configuration. To override, make a copy: 32 | 33 | conf := jsonfmt.Default 34 | conf.CommentLine = `#` 35 | content = jsonfmt.FormatBytes(conf, content) 36 | 37 | See `Conf` for details. 38 | */ 39 | var Default = Conf{ 40 | Indent: ` `, 41 | Width: 80, 42 | CommentLine: `//`, 43 | CommentBlockStart: `/*`, 44 | CommentBlockEnd: `*/`, 45 | TrailingComma: false, 46 | StripComments: false, 47 | } 48 | 49 | /* 50 | Configuration passed to `Format`. See the variable `Default`. 51 | 52 | `.Indent` enables multi-line output. When empty, `jsonfmt` will not emit 53 | separator spaces or newlines, except at the end of single-line comments. 54 | When non-empty, `jsonfmt` will emit separator spaces, newlines, and indents 55 | for contents of lists and dicts. To enforce single-line output, use 56 | `.Indent = ""` and `.StripComments = true`. 57 | 58 | `.Width` is the width limit for lists and dicts. If 0, then depending on other 59 | configuration, `jsonfmt` will format lists and dicts either always in 60 | multi-line mode, or always in single-line mode. If > 0, then `jsonfmt` will 61 | attempt to format each list or dict entirely on a single line until the width 62 | limit, falling back on multi-line mode when exceeding the width limit. Note 63 | that multi-line mode also requires non-empty `.Indent`. 64 | 65 | `.CommentLine` starts a single-line comment. If empty, single-line comments 66 | won't be detected, and will be treated as arbitrary JSON content. 67 | 68 | `.CommentBlockStart` and `.CommentBlockEnd` enable support for block comments. 69 | If both are non-empty, block comments are detected. Nested block comments are 70 | supported. If at least one is empty, then the other will be ignored, block 71 | comments will not be detected, and will be treated as arbitrary JSON content. 72 | 73 | `.TrailingComma` enables trailing commas for last elements in dicts and lists in 74 | multi-line mode. In single-line mode, trailing commas are always omitted. 75 | 76 | `.StripComments` omits all comments from the output. To enforce single-line 77 | output, specify this together with `.Indent = ""`. When single-line comments 78 | are not omitted from the output, they cause the output to contain newlines, 79 | because each single-line comment must be followed by a newline. 80 | */ 81 | type Conf struct { 82 | Indent string `json:"indent"` 83 | Width uint64 `json:"width"` 84 | CommentLine string `json:"commentLine"` 85 | CommentBlockStart string `json:"commentBlockStart"` 86 | CommentBlockEnd string `json:"commentBlockEnd"` 87 | TrailingComma bool `json:"trailingComma"` 88 | StripComments bool `json:"stripComments"` 89 | } 90 | 91 | const ( 92 | separator = ' ' 93 | newline = '\n' 94 | ) 95 | 96 | // Describes various interchangeable text types. 97 | type Text interface{ ~string | ~[]byte } 98 | 99 | // Formats JSON according to the config. See `Conf`. 100 | func Format[Out, Src Text](conf Conf, src Src) Out { 101 | fmter := fmter{source: text[string](src), conf: conf} 102 | fmter.top() 103 | return text[Out](fmter.buf.Bytes()) 104 | } 105 | 106 | // Formats JSON text according to config, returning a string. 107 | func FormatString[Src Text](conf Conf, src Src) string { 108 | return Format[string](conf, src) 109 | } 110 | 111 | // Formats JSON text according to config, returning bytes. 112 | func FormatBytes[Src Text](conf Conf, src Src) []byte { 113 | return Format[[]byte](conf, src) 114 | } 115 | 116 | /* 117 | Shortcut that combines formatting with `json.Unmarshal`. Allows to decode JSON 118 | with comments or invalid punctuation, such as trailing commas. Slower than 119 | simply using `json.Unmarshal`. Avoid this when your input is guaranteed to be 120 | valid JSON, or when you should be enforcing valid JSON. 121 | */ 122 | func Unmarshal[Src Text](src Src, out any) error { 123 | return json.Unmarshal(Format[[]byte](Conf{}, src), out) 124 | } 125 | 126 | type fmter struct { 127 | source string 128 | cursor int 129 | conf Conf 130 | buf bytes.Buffer 131 | indent int 132 | row int 133 | col int 134 | discard bool 135 | snapshot *fmter 136 | } 137 | 138 | func (self *fmter) top() { 139 | for self.more() { 140 | if self.skipped() { 141 | continue 142 | } 143 | 144 | if self.isNextComment() { 145 | assert(self.scannedAny()) 146 | self.writeMaybeCommentNewline() 147 | continue 148 | } 149 | 150 | if self.scannedAny() { 151 | self.writeMaybeNewline() 152 | continue 153 | } 154 | 155 | self.skipChar() 156 | } 157 | } 158 | 159 | func (self *fmter) any() { 160 | if self.isNextByte('{') { 161 | self.dict() 162 | } else if self.isNextByte('[') { 163 | self.list() 164 | } else if self.isNextByte('"') { 165 | self.string() 166 | } else if self.isNextCommentLine() { 167 | self.commentSingle() 168 | } else if self.isNextCommentBlock() { 169 | self.commentMulti() 170 | } else { 171 | self.atom() 172 | } 173 | } 174 | 175 | func (self *fmter) scannedAny() bool { 176 | return self.scanned((*fmter).any) 177 | } 178 | 179 | func (self *fmter) dict() { 180 | if !self.preferSingle() || !self.scanned((*fmter).dictSingle) { 181 | self.dictMulti() 182 | } 183 | } 184 | 185 | func (self *fmter) dictSingle() { 186 | prev := self.snap() 187 | defer self.maybeRollback(prev) 188 | 189 | assert(self.isNextByte('{')) 190 | self.byte() 191 | key := true 192 | 193 | for self.more() { 194 | if self.isNextByte('}') { 195 | self.byte() 196 | return 197 | } 198 | 199 | if self.skipped() { 200 | continue 201 | } 202 | 203 | if self.isNextComment() { 204 | assert(self.scannedAny()) 205 | continue 206 | } 207 | 208 | if key { 209 | assert(self.scannedAny()) 210 | self.writeByte(':') 211 | self.writeMaybeSeparator() 212 | key = false 213 | continue 214 | } 215 | 216 | assert(self.scannedAny()) 217 | if self.hasNonCommentsBefore('}') { 218 | self.writeByte(',') 219 | self.writeMaybeSeparator() 220 | } 221 | key = true 222 | } 223 | } 224 | 225 | func (self *fmter) dictMulti() { 226 | assert(self.isNextByte('{')) 227 | self.indent++ 228 | self.byte() 229 | self.writeMaybeNewline() 230 | key := true 231 | 232 | for self.more() { 233 | if self.isNextByte('}') { 234 | self.indent-- 235 | self.writeMaybeNewlineIndent() 236 | self.byte() 237 | return 238 | } 239 | 240 | if self.skipped() { 241 | continue 242 | } 243 | 244 | if self.isNextComment() { 245 | self.writeMaybeCommentNewlineIndent() 246 | assert(self.scannedAny()) 247 | continue 248 | } 249 | 250 | if key { 251 | self.writeMaybeNewlineIndent() 252 | assert(self.scannedAny()) 253 | self.writeByte(':') 254 | self.writeMaybeSeparator() 255 | key = false 256 | continue 257 | } 258 | 259 | assert(self.scannedAny()) 260 | if self.hasNonCommentsBefore('}') { 261 | self.writeByte(',') 262 | } else { 263 | self.writeMaybeTrailingComma() 264 | } 265 | key = true 266 | } 267 | } 268 | 269 | func (self *fmter) list() { 270 | if !self.preferSingle() || !self.scanned((*fmter).listSingle) { 271 | self.listMulti() 272 | } 273 | } 274 | 275 | func (self *fmter) listSingle() { 276 | prev := self.snap() 277 | defer self.maybeRollback(prev) 278 | 279 | assert(self.isNextByte('[')) 280 | self.byte() 281 | 282 | for self.more() { 283 | if self.isNextByte(']') { 284 | self.byte() 285 | return 286 | } 287 | 288 | if self.skipped() { 289 | continue 290 | } 291 | 292 | if self.isNextComment() { 293 | assert(self.scannedAny()) 294 | continue 295 | } 296 | 297 | assert(self.scannedAny()) 298 | if self.hasNonCommentsBefore(']') { 299 | self.writeByte(',') 300 | self.writeMaybeSeparator() 301 | } 302 | } 303 | } 304 | 305 | func (self *fmter) listMulti() { 306 | assert(self.isNextByte('[')) 307 | self.indent++ 308 | self.byte() 309 | self.writeMaybeNewline() 310 | 311 | for self.more() { 312 | if self.isNextByte(']') { 313 | self.indent-- 314 | self.writeMaybeNewlineIndent() 315 | self.byte() 316 | return 317 | } 318 | 319 | if self.skipped() { 320 | continue 321 | } 322 | 323 | if self.isNextComment() { 324 | self.writeMaybeCommentNewlineIndent() 325 | assert(self.scannedAny()) 326 | continue 327 | } 328 | 329 | self.writeMaybeNewlineIndent() 330 | assert(self.scannedAny()) 331 | if self.hasNonCommentsBefore(']') { 332 | self.writeByte(',') 333 | } else { 334 | self.writeMaybeTrailingComma() 335 | } 336 | } 337 | } 338 | 339 | func (self *fmter) string() { 340 | assert(self.isNextByte('"')) 341 | self.byte() 342 | 343 | for self.more() { 344 | if self.isNextByte('"') { 345 | self.byte() 346 | return 347 | } 348 | 349 | if self.isNextByte('\\') { 350 | self.byte() 351 | if self.more() { 352 | self.char() 353 | } 354 | continue 355 | } 356 | 357 | self.char() 358 | } 359 | } 360 | 361 | func (self *fmter) commentSingle() { 362 | prefix := self.nextCommentLinePrefix() 363 | assert(prefix != ``) 364 | 365 | if self.conf.StripComments { 366 | self.setDiscard(true) 367 | defer self.setDiscard(false) 368 | } 369 | 370 | self.strInc(prefix) 371 | 372 | for self.more() { 373 | if self.isNextPrefix("\r\n") { 374 | self.skipString("\r\n") 375 | self.writeNewline() 376 | return 377 | } 378 | 379 | if self.isNextByte('\n') || self.isNextByte('\r') { 380 | self.skipByte() 381 | self.writeNewline() 382 | return 383 | } 384 | 385 | self.char() 386 | } 387 | } 388 | 389 | func (self *fmter) commentMulti() { 390 | prefix, suffix := self.nextCommentBlockPrefixSuffix() 391 | assert(prefix != `` && suffix != ``) 392 | 393 | if self.conf.StripComments { 394 | self.setDiscard(true) 395 | defer self.setDiscard(false) 396 | } 397 | 398 | self.strInc(prefix) 399 | level := 1 400 | 401 | for self.more() { 402 | if self.isNextPrefix(suffix) { 403 | self.strInc(suffix) 404 | level-- 405 | if level == 0 { 406 | return 407 | } 408 | continue 409 | } 410 | 411 | if self.isNextPrefix(prefix) { 412 | self.strInc(prefix) 413 | level++ 414 | continue 415 | } 416 | 417 | self.char() 418 | } 419 | } 420 | 421 | func (self *fmter) atom() { 422 | for self.more() && !self.isNextSpace() && !self.isNextTerminal() { 423 | self.char() 424 | } 425 | } 426 | 427 | func (self *fmter) char() { 428 | char, size := utf8.DecodeRuneInString(self.rest()) 429 | assert(size > 0) 430 | self.writeRune(char) 431 | self.cursor += size 432 | } 433 | 434 | func (self *fmter) byte() { 435 | self.writeByte(self.source[self.cursor]) 436 | self.cursor++ 437 | } 438 | 439 | // Performance seems fine, probably because `bytes.Buffer` short-circuits ASCII runes. 440 | func (self *fmter) writeByte(char byte) { 441 | self.writeRune(rune(char)) 442 | } 443 | 444 | // ALL writes must call this function. 445 | func (self *fmter) writeRune(char rune) { 446 | if self.discard { 447 | return 448 | } 449 | 450 | if char == '\n' || char == '\r' { 451 | self.row++ 452 | self.col = 0 453 | } else { 454 | self.col++ 455 | } 456 | 457 | self.buf.WriteRune(char) 458 | 459 | if self.snapshot != nil && self.exceedsLine(self.snapshot) { 460 | panic(rollback) 461 | } 462 | } 463 | 464 | func (self *fmter) writeString(str string) { 465 | for _, char := range str { 466 | self.writeRune(char) 467 | } 468 | } 469 | 470 | func (self *fmter) writeMaybeSeparator() { 471 | if self.whitespace() { 472 | self.writeByte(separator) 473 | } 474 | } 475 | 476 | func (self *fmter) writeMaybeTrailingComma() { 477 | if self.conf.TrailingComma { 478 | self.writeByte(',') 479 | } 480 | } 481 | 482 | func (self *fmter) writeMaybeNewline() { 483 | if self.whitespace() && !self.hasNewlineSuffix() { 484 | self.writeByte(newline) 485 | } 486 | } 487 | 488 | func (self *fmter) writeNewline() { 489 | if !self.wrote((*fmter).writeMaybeNewline) { 490 | self.writeByte(newline) 491 | } 492 | } 493 | 494 | func (self *fmter) writeIndent() { 495 | for ind := 0; ind < self.indent; ind++ { 496 | self.writeString(self.conf.Indent) 497 | } 498 | } 499 | 500 | func (self *fmter) writeMaybeNewlineIndent() { 501 | if self.whitespace() { 502 | self.writeMaybeNewline() 503 | self.writeIndent() 504 | } 505 | } 506 | 507 | func (self *fmter) writeMaybeCommentNewlineIndent() { 508 | if !self.conf.StripComments { 509 | self.writeMaybeNewlineIndent() 510 | } 511 | } 512 | 513 | func (self *fmter) writeMaybeCommentNewline() { 514 | if !self.conf.StripComments { 515 | self.writeMaybeNewline() 516 | } 517 | } 518 | 519 | func (self *fmter) nextCommentLinePrefix() string { 520 | prefix := self.conf.CommentLine 521 | if prefix != `` && strings.HasPrefix(self.rest(), prefix) { 522 | return prefix 523 | } 524 | return `` 525 | } 526 | 527 | func (self *fmter) nextCommentBlockPrefixSuffix() (string, string) { 528 | prefix := self.conf.CommentBlockStart 529 | suffix := self.conf.CommentBlockEnd 530 | if prefix != `` && suffix != `` && strings.HasPrefix(self.rest(), prefix) { 531 | return prefix, suffix 532 | } 533 | return ``, `` 534 | } 535 | 536 | func (self *fmter) hasNonCommentsBefore(char byte) bool { 537 | prev := *self 538 | defer self.reset(&prev) 539 | 540 | for self.more() { 541 | if self.isNextByte(char) { 542 | return false 543 | } 544 | 545 | if self.skipped() { 546 | continue 547 | } 548 | 549 | if self.isNextComment() { 550 | assert(self.scannedAny()) 551 | continue 552 | } 553 | 554 | return true 555 | } 556 | 557 | return false 558 | } 559 | 560 | func (self *fmter) reset(prev *fmter) { 561 | self.cursor = prev.cursor 562 | self.indent = prev.indent 563 | self.row = prev.row 564 | self.col = prev.col 565 | self.buf.Truncate(prev.buf.Len()) 566 | } 567 | 568 | // Causes an escape and a minor heap allocation, but this isn't our bottleneck. 569 | // Ensuring stack allocation in this particular case seems to have no effect on 570 | // performance. 571 | func (self *fmter) snap() *fmter { 572 | prev := self.snapshot 573 | snapshot := *self 574 | self.snapshot = &snapshot 575 | return prev 576 | } 577 | 578 | var rollback = new(struct{}) 579 | 580 | func (self *fmter) maybeRollback(prev *fmter) { 581 | snapshot := self.snapshot 582 | self.snapshot = prev 583 | 584 | val := recover() 585 | if val == rollback { 586 | self.reset(snapshot) 587 | } else if val != nil { 588 | panic(val) 589 | } 590 | } 591 | 592 | // Used for `defer`. 593 | func (self *fmter) setDiscard(val bool) { 594 | self.discard = val 595 | } 596 | 597 | func (self *fmter) more() bool { 598 | return self.left() > 0 599 | } 600 | 601 | func (self *fmter) left() int { 602 | return len(self.source) - self.cursor 603 | } 604 | 605 | func (self *fmter) headByte() byte { 606 | if self.cursor < len(self.source) { 607 | return self.source[self.cursor] 608 | } 609 | return 0 610 | } 611 | 612 | func (self *fmter) rest() string { 613 | if self.more() { 614 | return self.source[self.cursor:] 615 | } 616 | return `` 617 | } 618 | 619 | func (self *fmter) isNextPrefix(prefix string) bool { 620 | return strings.HasPrefix(self.rest(), prefix) 621 | } 622 | 623 | func (self *fmter) isNextByte(char byte) bool { 624 | return self.headByte() == char 625 | } 626 | 627 | func (self *fmter) isNextSpace() bool { 628 | return self.isNextByte(' ') || self.isNextByte('\t') || self.isNextByte('\v') || 629 | self.isNextByte('\n') || self.isNextByte('\r') 630 | } 631 | 632 | /* 633 | We skip punctuation and insert it ourselves where appropriate. This allows us to 634 | automatically fix missing or broken punctuation. The user can write lists or 635 | dicts without punctuation, and we'll insert it. In JSON, this is completely 636 | unambiguous. 637 | 638 | Skipping `:` in lists also assists in the edge case of converting between lists 639 | and dicts. 640 | */ 641 | func (self *fmter) isNextPunctuation() bool { 642 | return self.isNextByte(',') || self.isNextByte(':') 643 | } 644 | 645 | func (self *fmter) isNextCommentLine() bool { 646 | return self.nextCommentLinePrefix() != `` 647 | } 648 | 649 | func (self *fmter) isNextCommentBlock() bool { 650 | prefix, suffix := self.nextCommentBlockPrefixSuffix() 651 | return prefix != `` && suffix != `` 652 | } 653 | 654 | func (self *fmter) isNextTerminal() bool { 655 | return self.isNextByte('{') || 656 | self.isNextByte('}') || 657 | self.isNextByte('[') || 658 | self.isNextByte(']') || 659 | self.isNextByte(',') || 660 | self.isNextByte(':') || 661 | self.isNextByte('"') || 662 | self.isNextComment() 663 | } 664 | 665 | func (self *fmter) isNextComment() bool { 666 | return self.isNextCommentLine() || self.isNextCommentBlock() 667 | } 668 | 669 | var ( 670 | bytesLf = []byte("\n") 671 | bytesCr = []byte("\r") 672 | ) 673 | 674 | func (self *fmter) hasNewlineSuffix() bool { 675 | content := self.buf.Bytes() 676 | return bytes.HasSuffix(content, bytesLf) || bytes.HasSuffix(content, bytesCr) 677 | } 678 | 679 | func (self *fmter) exceedsLine(prev *fmter) bool { 680 | return self.row > prev.row || self.conf.Width > 0 && self.col > int(self.conf.Width) 681 | } 682 | 683 | func (self *fmter) skipByte() { 684 | self.cursor++ 685 | } 686 | 687 | func (self *fmter) skipChar() { 688 | _, size := utf8.DecodeRuneInString(self.rest()) 689 | self.cursor += size 690 | } 691 | 692 | func (self *fmter) skipString(str string) { 693 | self.skipNBytes(len(str)) 694 | } 695 | 696 | func (self *fmter) skipNBytes(n int) { 697 | self.cursor += n 698 | } 699 | 700 | func (self *fmter) strInc(str string) { 701 | self.writeString(str) 702 | self.skipString(str) 703 | } 704 | 705 | func (self *fmter) scanned(fun func(*fmter)) bool { 706 | start := self.cursor 707 | fun(self) 708 | return self.cursor > start 709 | } 710 | 711 | func (self *fmter) wrote(fun func(*fmter)) bool { 712 | start := self.buf.Len() 713 | fun(self) 714 | return self.buf.Len() > start 715 | } 716 | 717 | func (self *fmter) skipped() bool { 718 | if self.isNextSpace() || self.isNextPunctuation() { 719 | self.skipByte() 720 | return true 721 | } 722 | return false 723 | } 724 | 725 | func (self *fmter) preferSingle() bool { 726 | return self.conf.Width > 0 727 | } 728 | 729 | func (self *fmter) whitespace() bool { 730 | return self.conf.Indent != `` 731 | } 732 | 733 | // Allocation-free conversion between two text types. 734 | func text[Out, Src Text](src Src) Out { return *(*Out)(unsafe.Pointer(&src)) } 735 | 736 | func assert(ok bool) { 737 | if !ok { 738 | panic(`[jsonfmt] internal error: failed a condition that should never be failed, see the stacktrace`) 739 | } 740 | } 741 | -------------------------------------------------------------------------------- /jsonfmt/cmd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Command line tool for jsonfmt. 3 | 4 | Installation: 5 | 6 | go get -u github.com/mitranim/jsonfmt 7 | 8 | Usage: 9 | 10 | jsonfmt -h 11 | 12 | Source and readme: https://github.com/mitranim/jsonfmt. 13 | */ 14 | package main 15 | 16 | import ( 17 | "flag" 18 | "fmt" 19 | "io" 20 | "os" 21 | 22 | "github.com/mitranim/jsonfmt" 23 | ) 24 | 25 | const help = `jsonfmt is a command-line JSON formatter. It reads from stdin and 26 | writes to stdout. For files, use pipe and redirect: 27 | 28 | cat .json | jsonfmt 29 | cat .json | jsonfmt > .json 30 | 31 | In addition to CLI, it's also available as a Go library: 32 | 33 | https://github.com/mitranim/jsonfmt 34 | 35 | Flags: 36 | 37 | ` 38 | 39 | func main() { 40 | conf := jsonfmt.Default 41 | 42 | flag.Usage = usage 43 | flag.StringVar(&conf.Indent, `i`, conf.Indent, `indentation`) 44 | flag.Uint64Var(&conf.Width, `w`, conf.Width, `line width`) 45 | flag.StringVar(&conf.CommentLine, `l`, conf.CommentLine, `beginning of line comment`) 46 | flag.StringVar(&conf.CommentBlockStart, `b`, conf.CommentBlockStart, `beginning of block comment`) 47 | flag.StringVar(&conf.CommentBlockEnd, `e`, conf.CommentBlockEnd, `end of block comment`) 48 | flag.BoolVar(&conf.TrailingComma, `t`, conf.TrailingComma, `trailing commas when multiline`) 49 | flag.BoolVar(&conf.StripComments, `s`, conf.StripComments, `strip comments`) 50 | flag.Parse() 51 | 52 | args := flag.Args() 53 | 54 | if len(args) > 0 { 55 | if args[0] == `help` { 56 | usage() 57 | os.Exit(0) 58 | return 59 | } 60 | 61 | fmt.Fprintf(os.Stderr, `[jsonfmt] unexpected arguments %q`, args) 62 | os.Exit(1) 63 | return 64 | } 65 | 66 | src, err := io.ReadAll(os.Stdin) 67 | if err != nil { 68 | fmt.Fprintf(os.Stderr, `[jsonfmt] failed to read: %v`, err) 69 | os.Exit(1) 70 | return 71 | } 72 | 73 | _, err = os.Stdout.Write(jsonfmt.FormatBytes(conf, src)) 74 | if err != nil { 75 | fmt.Fprintf(os.Stderr, `[jsonfmt] failed to write: %v`, err) 76 | os.Exit(1) 77 | } 78 | } 79 | 80 | func usage() { 81 | fmt.Fprint(os.Stderr, help) 82 | flag.PrintDefaults() 83 | } 84 | -------------------------------------------------------------------------------- /jsonfmt_test.go: -------------------------------------------------------------------------------- 1 | package jsonfmt 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "os" 8 | "path/filepath" 9 | "reflect" 10 | "strings" 11 | "testing" 12 | ) 13 | 14 | const ( 15 | DIR_TESTDATA = `testdata` 16 | FMTED_SUFFIX = `_fmted` 17 | STD_COMPATIBLE_FILE = `inp_long_pure.json` 18 | ) 19 | 20 | func Benchmark_json_Indent(b *testing.B) { 21 | content := readTestFile(b, STD_COMPATIBLE_FILE) 22 | b.ResetTimer() 23 | 24 | for i := 0; i < b.N; i++ { 25 | var buf bytes.Buffer 26 | try(json.Indent(&buf, content, ``, ` `)) 27 | } 28 | } 29 | 30 | func BenchmarkFormat(b *testing.B) { 31 | content := readTestFile(b, STD_COMPATIBLE_FILE) 32 | b.ResetTimer() 33 | 34 | for i := 0; i < b.N; i++ { 35 | _ = FormatBytes(Default, content) 36 | } 37 | } 38 | 39 | func TestMain(m *testing.M) { 40 | try(deleteTestFiles(`*` + FMTED_SUFFIX + `.*`)) 41 | 42 | code := m.Run() 43 | if code == 0 { 44 | try(deleteTestFiles(`*` + FMTED_SUFFIX + `.*`)) 45 | } 46 | 47 | os.Exit(code) 48 | } 49 | 50 | // Sanity check for the test itself. 51 | func Test_json_Indent(t *testing.T) { 52 | const src = STD_COMPATIBLE_FILE 53 | content := readTestFile(t, src) 54 | 55 | var buf bytes.Buffer 56 | try(json.Indent(&buf, content, ``, ` `)) 57 | 58 | eqFile(t, src, `out_long_multi.json`, buf.Bytes()) 59 | } 60 | 61 | func TestFormat_hybrid(t *testing.T) { 62 | conf := Default 63 | conf.TrailingComma = true 64 | 65 | const src = `inp_long_comments.json` 66 | input := readTestFile(t, src) 67 | output := FormatBytes(conf, input) 68 | eqFile(t, src, `out_long_hybrid_commas_comments.json`, output) 69 | } 70 | 71 | func TestFormat_hybrid_strip_comments(t *testing.T) { 72 | conf := Default 73 | conf.TrailingComma = true 74 | conf.StripComments = true 75 | 76 | const src = `inp_long_comments.json` 77 | input := readTestFile(t, src) 78 | output := FormatBytes(conf, input) 79 | eqFile(t, src, `out_long_hybrid_commas.json`, output) 80 | } 81 | 82 | func TestFormat_insert_punctuation(t *testing.T) { 83 | conf := Default 84 | conf.TrailingComma = true 85 | 86 | const src = `inp_short_nopunc.json` 87 | input := readTestFile(t, src) 88 | output := FormatBytes(conf, input) 89 | eqFile(t, src, `out_short_punc.json`, output) 90 | } 91 | 92 | func TestFormat_single_line_with_comments(t *testing.T) { 93 | conf := Default 94 | conf.Indent = `` 95 | conf.StripComments = false 96 | 97 | const src = `inp_long_comments.json` 98 | input := readTestFile(t, src) 99 | output := FormatBytes(conf, input) 100 | eqFile(t, src, `out_long_single_comments.json`, output) 101 | } 102 | 103 | func TestFormat_single_line_strip_comments(t *testing.T) { 104 | conf := Default 105 | conf.Indent = `` 106 | conf.StripComments = true 107 | 108 | const src = `inp_long_comments.json` 109 | input := readTestFile(t, src) 110 | output := FormatBytes(conf, input) 111 | eqFile(t, src, `out_long_single_stripped.json`, output) 112 | } 113 | 114 | // TODO consider desired single-line behavior. 115 | func TestFormat_block_comment_multi_line(t *testing.T) { 116 | conf := Default 117 | 118 | const src = `inp_comment_block.json` 119 | input := readTestFile(t, src) 120 | output := FormatBytes(conf, input) 121 | eqFile(t, src, `out_comment_block_multi_line.json`, output) 122 | } 123 | 124 | // TODO consider desired single-line behavior. 125 | func TestFormat_block_comment_multi_line_nested(t *testing.T) { 126 | conf := Default 127 | conf.Width = 0 128 | 129 | const src = `inp_comment_block_nested.json` 130 | input := readTestFile(t, src) 131 | output := FormatBytes(conf, input) 132 | eqFile(t, src, `out_comment_block_nested_multi_line.json`, output) 133 | } 134 | 135 | func TestFormat_json_lines(t *testing.T) { 136 | conf := Default 137 | conf.StripComments = true 138 | 139 | const src = `inp_lines.json` 140 | input := readTestFile(t, src) 141 | output := FormatBytes(conf, input) 142 | 143 | eqFile(t, src, `out_lines.json`, output) 144 | } 145 | 146 | // This used to hang forever. 147 | func TestFormat_primitive(t *testing.T) { 148 | input := []byte(`0`) 149 | expected := []byte("0\n") 150 | fmted := FormatBytes(Default, input) 151 | 152 | if bytes.Equal(expected, fmted) { 153 | return 154 | } 155 | 156 | t.Fatalf(strings.TrimSpace(` 157 | format mismatch 158 | input: %q 159 | expected output: %q 160 | actual output: %q 161 | `), input, expected, fmted) 162 | } 163 | 164 | func TestUnmarshal(t *testing.T) { 165 | type TarGlobal struct { 166 | CheckForUpdatesOnStartup bool `json:"check_for_updates_on_startup"` 167 | ShowInMenuBar bool `json:"show_in_menu_bar"` 168 | ShowProfileNameInMenuBar bool `json:"show_profile_name_in_menu_bar"` 169 | } 170 | 171 | type TarProfile struct { 172 | // Fields elided for simplicity. 173 | } 174 | 175 | type Tar struct { 176 | Global TarGlobal `json:"global"` 177 | Profiles []TarProfile `json:"profiles"` 178 | } 179 | 180 | var tar Tar 181 | try(Unmarshal(readTestFile(t, `inp_short_nopunc.json`), &tar)) 182 | 183 | eq(t, tar, Tar{ 184 | Global: TarGlobal{CheckForUpdatesOnStartup: true}, 185 | Profiles: []TarProfile{{}}, 186 | }) 187 | } 188 | 189 | func eq(t testing.TB, exp, act interface{}) { 190 | if !reflect.DeepEqual(exp, act) { 191 | t.Fatalf(` 192 | expected (detailed): 193 | %#[1]v 194 | actual (detailed): 195 | %#[2]v 196 | expected (simple): 197 | %[1]v 198 | actual (simple): 199 | %[2]v 200 | `, exp, act) 201 | } 202 | } 203 | 204 | func eqFile(t testing.TB, pathSrc string, pathExpected string, fmtedContent []byte) { 205 | expectedContent := readTestFile(t, pathExpected) 206 | 207 | if bytes.Equal(expectedContent, fmtedContent) { 208 | return 209 | } 210 | 211 | pathFmted := appendToName(pathExpected, FMTED_SUFFIX) 212 | writeTestFile(t, pathFmted, fmtedContent) 213 | 214 | t.Fatalf(strings.TrimSpace(` 215 | format mismatch 216 | source: %q 217 | expected output: %q 218 | actual output: %q 219 | `), 220 | testFilePath(pathSrc), 221 | testFilePath(pathExpected), 222 | testFilePath(pathFmted)) 223 | } 224 | 225 | func deleteTestFiles(pattern string) error { 226 | matches, err := filepath.Glob(testFilePath(pattern)) 227 | if err != nil { 228 | panic(fmt.Errorf(`failed to find files by pattern %q: %w`, pattern, err)) 229 | } 230 | 231 | for _, path := range matches { 232 | err := os.Remove(path) 233 | if err != nil { 234 | panic(fmt.Errorf(`failed to delete %q: %w`, path, err)) 235 | } 236 | } 237 | 238 | return nil 239 | } 240 | 241 | func readTestFile(t testing.TB, name string) []byte { 242 | path := testFilePath(name) 243 | content, err := os.ReadFile(path) 244 | if err != nil { 245 | t.Fatalf(`failed to read test file at %q: %+v`, path, err) 246 | } 247 | return content 248 | } 249 | 250 | func writeTestFile(t testing.TB, name string, content []byte) { 251 | path := testFilePath(name) 252 | err := os.WriteFile(path, content, os.ModePerm) 253 | if err != nil { 254 | t.Fatalf(`failed to write %q: %+v`, path, err) 255 | } 256 | } 257 | 258 | func testFilePath(name string) string { 259 | return filepath.Join(DIR_TESTDATA, name) 260 | } 261 | 262 | func appendToName(path string, suffix string) string { 263 | dir, base, ext := splitPath(path) 264 | return dir + base + suffix + ext 265 | } 266 | 267 | func splitPath(path string) (string, string, string) { 268 | dir, file := filepath.Split(path) 269 | 270 | ext := filepath.Ext(file) 271 | base := strings.TrimSuffix(file, ext) 272 | 273 | if base == `` && ext != `` { 274 | return dir, ext, `` 275 | } 276 | 277 | return dir, base, ext 278 | } 279 | 280 | func try(err error) { 281 | if err != nil { 282 | fmt.Fprintf(os.Stderr, `%+v`, err) 283 | os.Exit(1) 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | Flexible JSON formatter. Features: 4 | 5 | * Preserves order. 6 | * Supports comments (configurable). 7 | * Supports trailing commas (configurable). 8 | * Supports max width (configurable). 9 | * For dicts and lists: single-line until given width, multi-line after 10 | exceeding said width. 11 | * Fixes missing or broken punctuation. 12 | * Tiny Go library. 13 | * Optional tiny CLI. 14 | * No dependencies. 15 | 16 | See API documentation at https://godoc.org/github.com/mitranim/jsonfmt. 17 | 18 | Current limitations: 19 | 20 | * Always permissive. Unrecognized non-whitespace is treated as arbitrary content on par with strings, numbers, etc. 21 | * Slower than `json.Indent` from the Go standard library. 22 | * Input must be UTF-8. 23 | * No streaming support. Input and output are `[]byte` or `string`. 24 | * Streaming support could be added on demand. 25 | 26 | ## Installation 27 | 28 | ### Library 29 | 30 | To use this as a library, simply import it: 31 | 32 | ```go 33 | import "github.com/mitranim/jsonfmt" 34 | 35 | var formatted string = jsonfmt.Format[string](jsonfmt.Default, `{}`) 36 | var formatted string = jsonfmt.FormatString(jsonfmt.Default, `{}`) 37 | var formatted []byte = jsonfmt.FormatBytes(jsonfmt.Default, `{}`) 38 | ``` 39 | 40 | ### CLI 41 | 42 | First, install Go: https://golang.org. Then run this: 43 | 44 | ```sh 45 | go install github.com/mitranim/jsonfmt/jsonfmt@latest 46 | ``` 47 | 48 | This will compile the executable into `$GOPATH/bin/jsonfmt`. Make sure `$GOPATH/bin` is in your `$PATH` so the shell can discover the `jsonfmt` command. For example, my `~/.profile` contains this: 49 | 50 | ```sh 51 | export GOPATH="$HOME/go" 52 | export PATH="$GOPATH/bin:$PATH" 53 | ``` 54 | 55 | Alternatively, you can run the executable using the full path. At the time of writing, `~/go` is the default `$GOPATH` for Go installations. Some systems may have a different one. 56 | 57 | ```sh 58 | ~/go/bin/jsonfmt 59 | ``` 60 | 61 | ## Usage 62 | 63 | See the library documentation on https://godoc.org/github.com/mitranim/jsonfmt. 64 | 65 | For CLI usage, run `jsonfmt -h`. 66 | 67 | ## Examples 68 | 69 | **Supports comments and trailing commas** (all configurable): 70 | 71 | ```jsonc 72 | {// Line comment 73 | "one": "two", /* Block comment */ "three": 40} 74 | ``` 75 | 76 | Output: 77 | 78 | ```jsonc 79 | { 80 | // Line comment 81 | "one": "two", 82 | /* Block comment */ 83 | "three": 40, 84 | } 85 | ``` 86 | 87 | **Single-line until width limit** (configurable): 88 | 89 | ```jsonc 90 | { 91 | "one": {"two": ["three"], "four": ["five"]}, 92 | "six": {"seven": ["eight"], "nine": ["ten"], "eleven": ["twelve"], "thirteen": ["fourteen"]} 93 | } 94 | ``` 95 | 96 | Output: 97 | 98 | ```jsonc 99 | { 100 | "one": {"two": ["three"], "four": ["five"]}, 101 | "six": { 102 | "seven": ["eight"], 103 | "nine": ["ten"], 104 | "eleven": ["twelve"], 105 | "thirteen": ["fourteen"], 106 | }, 107 | } 108 | ``` 109 | 110 | **Fix missing or broken punctuation**: 111 | 112 | ```jsonc 113 | {"one" "two" "three" {"four" "five"} "six" ["seven": "eight"]},,, 114 | ``` 115 | 116 | Output: 117 | 118 | ```jsonc 119 | {"one": "two", "three": {"four": "five"}, "six": ["seven", "eight"]} 120 | ``` 121 | 122 | ## License 123 | 124 | https://unlicense.org 125 | 126 | ## Misc 127 | 128 | I'm receptive to suggestions. If this library _almost_ satisfies you but needs changes, open an issue or chat me up. Contacts: https://mitranim.com/#contacts 129 | -------------------------------------------------------------------------------- /testdata/inp_comment_block.json: -------------------------------------------------------------------------------- 1 | /* block comment in JSON */ "valid JSON" 2 | -------------------------------------------------------------------------------- /testdata/inp_comment_block_nested.json: -------------------------------------------------------------------------------- 1 | /* block comment one */ [ 2 | /* block comment two */ { 3 | /* block comment three */ "four": "five" 4 | } 5 | ] 6 | -------------------------------------------------------------------------------- /testdata/inp_lines.json: -------------------------------------------------------------------------------- 1 | // Line comment 2 | { "ae02a7":"9552f6", } 3 | /* Block comment */ 4 | { "25fa37":"260ec9", } 5 | { "41a676":"9023a1", } 6 | { "9e5c7b":"2cb31f", } 7 | { "694bba":"d3cdb8", } 8 | { "27b475":"c1dcaa", } 9 | { "1f3247":"fddee8", } 10 | { "844234":"42a47a", } 11 | { "5102ba":"a7883d", } 12 | { "6dec88":"280666", } 13 | { "04e957":"658835", } 14 | { "ca2052":"2a1e1a", } 15 | { "8aae66":"9d11f2", } 16 | { "6c2e42":"451dc4", } 17 | { "619c21":"8498e2", } 18 | { "12c089":"4af0e7", } 19 | { "4500eb":"980361", } 20 | { "5bd20c":"54085d", } 21 | { "19412f":"11253d", } -------------------------------------------------------------------------------- /testdata/inp_long_comments.json: -------------------------------------------------------------------------------- 1 | // First single line comment 2 | { 3 | "global": { 4 | "check_for_updates_on_startup": true, 5 | "show_in_menu_bar": false, 6 | "show_profile_name_in_menu_bar": false 7 | }, 8 | "profiles": [ 9 | { 10 | "complex_modifications": { 11 | "parameters": { 12 | /* First multi line comment on single line */ 13 | "basic.simultaneous_threshold_milliseconds": 50, 14 | "basic.to_delayed_action_delay_milliseconds": 500, 15 | "basic.to_if_alone_timeout_milliseconds": 1000, 16 | "basic.to_if_held_down_threshold_milliseconds": 500 17 | }, 18 | "rules": [ 19 | { 20 | "description": "Custom Tweaks", 21 | "manipulators": [ 22 | { 23 | "from": { 24 | "key_code": "f19", 25 | "modifiers": { 26 | "mandatory": [ 27 | // Second single line comment 28 | "left_command" 29 | ] 30 | } 31 | }, 32 | "to": [ 33 | { 34 | "key_code": "vk_none" 35 | // Third single line comment 36 | } 37 | ], 38 | "type": "basic" 39 | }, 40 | { 41 | "from": { 42 | "key_code": "h", 43 | "modifiers": { 44 | "mandatory": [ 45 | "left_command" 46 | ], 47 | "optional": [ 48 | "shift", 49 | "control", 50 | "right_command" 51 | ] 52 | } 53 | }, 54 | "to": [ 55 | { 56 | "key_code": "left_arrow" 57 | } 58 | ], 59 | "type": "basic" 60 | }, 61 | { 62 | "from": { 63 | "key_code": "j", 64 | "modifiers": { 65 | "mandatory": [ 66 | "left_command" 67 | ], 68 | "optional": [ 69 | "shift", 70 | "fn", 71 | "control", 72 | "right_command" 73 | ] 74 | } 75 | }, 76 | "to": [ 77 | { 78 | "key_code": "down_arrow" 79 | } 80 | ], 81 | "type": "basic" 82 | }, 83 | { 84 | "from": { 85 | "key_code": "k", 86 | "modifiers": { 87 | "mandatory": [ 88 | "left_command" 89 | ], 90 | "optional": [ 91 | "shift", 92 | "fn", 93 | "control", 94 | "right_command" 95 | ] 96 | } 97 | }, 98 | "to": [ 99 | { 100 | "key_code": "up_arrow" 101 | } 102 | ], 103 | "type": "basic" 104 | }, 105 | { 106 | "from": { 107 | "key_code": "l", 108 | "modifiers": { 109 | "mandatory": [ 110 | "left_command" 111 | ], 112 | "optional": [ 113 | "shift", 114 | "control", 115 | "right_command" 116 | ] 117 | } 118 | }, 119 | "to": [ 120 | { 121 | "key_code": "right_arrow" 122 | } 123 | ], 124 | "type": "basic" 125 | }, 126 | { 127 | "from": { 128 | "key_code": "h", 129 | "modifiers": { 130 | "mandatory": [ 131 | "left_option" 132 | ], 133 | "optional": [ 134 | "shift" 135 | ] 136 | } 137 | }, 138 | "to": [ 139 | { 140 | "key_code": "left_arrow", 141 | "modifiers": [ 142 | "left_option" 143 | ] 144 | } 145 | ], 146 | "type": "basic" 147 | }, 148 | { 149 | "from": { 150 | "key_code": "j", 151 | "modifiers": { 152 | "mandatory": [ 153 | "left_option" 154 | ], 155 | "optional": [ 156 | "shift" 157 | ] 158 | } 159 | }, 160 | "to": [ 161 | { 162 | "key_code": "down_arrow", 163 | "modifiers": [ 164 | "left_option" 165 | ] 166 | } 167 | ], 168 | "type": "basic" 169 | }, 170 | { 171 | "from": { 172 | "key_code": "k", 173 | "modifiers": { 174 | "mandatory": [ 175 | "left_option" 176 | ], 177 | "optional": [ 178 | "shift" 179 | ] 180 | } 181 | }, 182 | "to": [ 183 | { 184 | "key_code": "up_arrow", 185 | "modifiers": [ 186 | "left_option" 187 | ] 188 | } 189 | ], 190 | "type": "basic" 191 | }, 192 | { 193 | "from": { 194 | "key_code": "l", 195 | "modifiers": { 196 | "mandatory": [ 197 | "left_option" 198 | ], 199 | "optional": [ 200 | "shift" 201 | ] 202 | } 203 | }, 204 | "to": [ 205 | { 206 | "key_code": "right_arrow", 207 | "modifiers": [ 208 | "left_option" 209 | ] 210 | } 211 | ], 212 | "type": "basic" 213 | }, 214 | { 215 | "from": { 216 | "key_code": "h", 217 | "modifiers": { 218 | "mandatory": [ 219 | "control" 220 | ] 221 | } 222 | }, 223 | "to": [ 224 | { 225 | "key_code": "delete_or_backspace" 226 | } 227 | ], 228 | "type": "basic" 229 | }, 230 | { 231 | "from": { 232 | "key_code": "j", 233 | "modifiers": { 234 | "mandatory": [ 235 | "control" 236 | ] 237 | } 238 | }, 239 | "to": [ 240 | { 241 | "key_code": "vk_none" 242 | } 243 | ], 244 | "type": "basic" 245 | }, 246 | { 247 | "from": { 248 | "key_code": "k", 249 | "modifiers": { 250 | "mandatory": [ 251 | "control" 252 | ] 253 | } 254 | }, 255 | "to": [ 256 | { 257 | "key_code": "vk_none" 258 | } 259 | ], 260 | "type": "basic" 261 | }, 262 | { 263 | "from": { 264 | "key_code": "l", 265 | "modifiers": { 266 | "mandatory": [ 267 | "control" 268 | ] 269 | } 270 | }, 271 | "to": [ 272 | { 273 | "key_code": "delete_forward" 274 | } 275 | ], 276 | "type": "basic" 277 | }, 278 | { 279 | "from": { 280 | "key_code": "h", 281 | "modifiers": { 282 | "mandatory": [ 283 | "control", 284 | "left_option" 285 | ] 286 | } 287 | }, 288 | "to": [ 289 | { 290 | "key_code": "delete_or_backspace", 291 | "modifiers": [ 292 | "left_option" 293 | ] 294 | } 295 | ], 296 | "type": "basic" 297 | }, 298 | { 299 | "from": { 300 | "key_code": "j", 301 | "modifiers": { 302 | "mandatory": [ 303 | "control", 304 | "left_option" 305 | ] 306 | } 307 | }, 308 | "to": [ 309 | { 310 | "key_code": "vk_none" 311 | } 312 | ], 313 | "type": "basic" 314 | }, 315 | { 316 | "from": { 317 | "key_code": "k", 318 | "modifiers": { 319 | "mandatory": [ 320 | "control", 321 | "left_option" 322 | ] 323 | } 324 | }, 325 | "to": [ 326 | { 327 | "key_code": "vk_none" 328 | } 329 | ], 330 | "type": "basic" 331 | }, 332 | { 333 | "from": { 334 | "key_code": "l", 335 | "modifiers": { 336 | "mandatory": [ 337 | "control", 338 | "left_option" 339 | ] 340 | } 341 | }, 342 | "to": [ 343 | { 344 | "key_code": "delete_forward", 345 | "modifiers": [ 346 | "left_option" 347 | ] 348 | } 349 | ], 350 | "type": "basic" 351 | }, 352 | { 353 | "from": { 354 | "key_code": "h", 355 | "modifiers": { 356 | "mandatory": [ 357 | "fn" 358 | ], 359 | "optional": [ 360 | "command", 361 | "shift" 362 | ] 363 | } 364 | }, 365 | "to": [ 366 | { 367 | "key_code": "left_arrow", 368 | "modifiers": [ 369 | "command" 370 | ] 371 | } 372 | ], 373 | "type": "basic" 374 | }, 375 | { 376 | "from": { 377 | "key_code": "j", 378 | "modifiers": { 379 | "mandatory": [ 380 | "fn" 381 | ], 382 | "optional": [ 383 | "shift" 384 | ] 385 | } 386 | }, 387 | "to": [ 388 | { 389 | "key_code": "down_arrow", 390 | "modifiers": [ 391 | "fn" 392 | ] 393 | } 394 | ], 395 | "type": "basic" 396 | }, 397 | { 398 | "from": { 399 | "key_code": "k", 400 | "modifiers": { 401 | "mandatory": [ 402 | "fn" 403 | ], 404 | "optional": [ 405 | "shift" 406 | ] 407 | } 408 | }, 409 | "to": [ 410 | { 411 | "key_code": "up_arrow", 412 | "modifiers": [ 413 | "fn" 414 | ] 415 | } 416 | ], 417 | "type": "basic" 418 | }, 419 | { 420 | "from": { 421 | "key_code": "l", 422 | "modifiers": { 423 | "mandatory": [ 424 | "fn" 425 | ], 426 | "optional": [ 427 | "command", 428 | "shift" 429 | ] 430 | } 431 | }, 432 | "to": [ 433 | { 434 | "key_code": "right_arrow", 435 | "modifiers": [ 436 | "command" 437 | ] 438 | } 439 | ], 440 | "type": "basic" 441 | }, 442 | { 443 | "from": { 444 | "key_code": "h", 445 | "modifiers": { 446 | "mandatory": [ 447 | "control", 448 | "fn" 449 | ] 450 | } 451 | }, 452 | "to": [ 453 | { 454 | "key_code": "delete_or_backspace", 455 | "modifiers": [ 456 | "command" 457 | ] 458 | } 459 | ], 460 | "type": "basic" 461 | }, 462 | { 463 | "from": { 464 | "key_code": "l", 465 | "modifiers": { 466 | "mandatory": [ 467 | "control", 468 | "fn" 469 | ] 470 | } 471 | }, 472 | "to": [ 473 | { 474 | "key_code": "delete_or_backspace", 475 | "modifiers": [ 476 | "command", 477 | "fn" 478 | ] 479 | } 480 | ], 481 | "type": "basic" 482 | }, 483 | { 484 | "from": { 485 | "key_code": "open_bracket", 486 | "modifiers": { 487 | "mandatory": ["left_control"] 488 | } 489 | }, 490 | "to": [ 491 | { 492 | "key_code": "open_bracket", 493 | "modifiers": ["shift", "command"] 494 | } 495 | ], 496 | "type": "basic" 497 | }, 498 | { 499 | "from": { 500 | "key_code": "close_bracket", 501 | "modifiers": { 502 | "mandatory": ["left_control"] 503 | } 504 | }, 505 | "to": [ 506 | { 507 | "key_code": "close_bracket", 508 | "modifiers": ["shift", "command"] 509 | } 510 | ], 511 | "type": "basic" 512 | }, 513 | { 514 | "from": { 515 | "key_code": "tab", 516 | "modifiers": { 517 | "mandatory": ["left_control"] 518 | } 519 | }, 520 | "to": [ 521 | { 522 | "key_code": "close_bracket", 523 | "modifiers": ["shift", "command"] 524 | } 525 | ], 526 | "type": "basic" 527 | }, 528 | { 529 | "from": { 530 | "key_code": "tab", 531 | "modifiers": { 532 | "mandatory": ["left_control", "shift"] 533 | } 534 | }, 535 | "to": [ 536 | { 537 | "key_code": "open_bracket", 538 | "modifiers": ["shift", "command"] 539 | } 540 | ], 541 | "type": "basic" 542 | } 543 | ] 544 | } 545 | ] 546 | }, 547 | "devices": [ 548 | { 549 | "disable_built_in_keyboard_if_exists": false, 550 | "fn_function_keys": [], 551 | "identifiers": { 552 | "is_keyboard": true, 553 | "is_pointing_device": false, 554 | "product_id": 632, 555 | "vendor_id": 1452 556 | }, 557 | "ignore": false, 558 | "keyboard_type": 0, 559 | "manipulate_caps_lock_led": true, 560 | "simple_modifications": [] 561 | }, 562 | { 563 | "disable_built_in_keyboard_if_exists": false, 564 | "fn_function_keys": [], 565 | "identifiers": { 566 | "is_keyboard": true, 567 | "is_pointing_device": false, 568 | "product_id": 34304, 569 | "vendor_id": 1452 570 | }, 571 | "ignore": true, 572 | "keyboard_type": 0, 573 | "manipulate_caps_lock_led": true, 574 | "simple_modifications": [] 575 | }, 576 | { 577 | "disable_built_in_keyboard_if_exists": false, 578 | "fn_function_keys": [], 579 | "identifiers": { 580 | "is_keyboard": true, 581 | "is_pointing_device": false, 582 | "product_id": 91, 583 | "vendor_id": 5426 584 | }, 585 | "ignore": true, 586 | "manipulate_caps_lock_led": false, 587 | "simple_modifications": [] 588 | }, 589 | { 590 | "disable_built_in_keyboard_if_exists": false, 591 | "fn_function_keys": [], 592 | "identifiers": { 593 | "is_keyboard": true, 594 | "is_pointing_device": false, 595 | "product_id": 50489, 596 | "vendor_id": 1133 597 | }, 598 | "ignore": true, 599 | "manipulate_caps_lock_led": false, 600 | "simple_modifications": [] 601 | } 602 | ], 603 | "fn_function_keys": [ 604 | { 605 | "from": { 606 | "key_code": "f1" 607 | }, 608 | "to": { 609 | "key_code": "display_brightness_decrement" 610 | } 611 | }, 612 | { 613 | "from": { 614 | "key_code": "f2" 615 | }, 616 | "to": { 617 | "key_code": "display_brightness_increment" 618 | } 619 | }, 620 | { 621 | "from": { 622 | "key_code": "f3" 623 | }, 624 | "to": { 625 | "key_code": "mission_control" 626 | } 627 | }, 628 | { 629 | "from": { 630 | "key_code": "f4" 631 | }, 632 | "to": { 633 | "key_code": "launchpad" 634 | } 635 | }, 636 | { 637 | "from": { 638 | "key_code": "f5" 639 | }, 640 | "to": { 641 | "key_code": "illumination_decrement" 642 | } 643 | }, 644 | { 645 | "from": { 646 | "key_code": "f6" 647 | }, 648 | "to": { 649 | "key_code": "illumination_increment" 650 | } 651 | }, 652 | { 653 | "from": { 654 | "key_code": "f7" 655 | }, 656 | "to": { 657 | "key_code": "rewind" 658 | } 659 | }, 660 | { 661 | "from": { 662 | "key_code": "f8" 663 | }, 664 | "to": { 665 | "key_code": "play_or_pause" 666 | } 667 | }, 668 | { 669 | "from": { 670 | "key_code": "f9" 671 | }, 672 | "to": { 673 | "key_code": "fastforward" 674 | } 675 | }, 676 | { 677 | "from": { 678 | "key_code": "f10" 679 | }, 680 | "to": { 681 | "key_code": "mute" 682 | } 683 | }, 684 | { 685 | "from": { 686 | "key_code": "f11" 687 | }, 688 | "to": { 689 | "key_code": "volume_decrement" 690 | } 691 | }, 692 | { 693 | "from": { 694 | "key_code": "f12" 695 | }, 696 | "to": { 697 | "key_code": "volume_increment" 698 | } 699 | } 700 | ], 701 | "name": "Default profile", 702 | "selected": true, 703 | "simple_modifications": [ 704 | { 705 | "from": { 706 | "key_code": "caps_lock" 707 | }, 708 | "to": { 709 | "key_code": "left_control" 710 | } 711 | }, 712 | { 713 | "from": { 714 | "key_code": "left_control" 715 | }, 716 | "to": { 717 | "key_code": "f19" 718 | } 719 | } 720 | ], 721 | "virtual_hid_keyboard": { 722 | "caps_lock_delay_milliseconds": 0, 723 | "country_code": 0, 724 | "keyboard_type": "ansi" 725 | } 726 | } 727 | ] 728 | } 729 | -------------------------------------------------------------------------------- /testdata/inp_long_pure.json: -------------------------------------------------------------------------------- 1 | { 2 | "global": { 3 | "check_for_updates_on_startup": true, 4 | "show_in_menu_bar": false, 5 | "show_profile_name_in_menu_bar": false 6 | }, 7 | "profiles": [ 8 | { 9 | "complex_modifications": { 10 | "parameters": { 11 | "basic.simultaneous_threshold_milliseconds": 50, 12 | "basic.to_delayed_action_delay_milliseconds": 500, 13 | "basic.to_if_alone_timeout_milliseconds": 1000, 14 | "basic.to_if_held_down_threshold_milliseconds": 500 15 | }, 16 | "rules": [ 17 | { 18 | "description": "Custom Tweaks", 19 | "manipulators": [ 20 | { 21 | "from": { 22 | "key_code": "f19", 23 | "modifiers": { 24 | "mandatory": [ 25 | "left_command" 26 | ] 27 | } 28 | }, 29 | "to": [ 30 | { 31 | "key_code": "vk_none" 32 | } 33 | ], 34 | "type": "basic" 35 | }, 36 | { 37 | "from": { 38 | "key_code": "h", 39 | "modifiers": { 40 | "mandatory": [ 41 | "left_command" 42 | ], 43 | "optional": [ 44 | "shift", 45 | "control", 46 | "right_command" 47 | ] 48 | } 49 | }, 50 | "to": [ 51 | { 52 | "key_code": "left_arrow" 53 | } 54 | ], 55 | "type": "basic" 56 | }, 57 | { 58 | "from": { 59 | "key_code": "j", 60 | "modifiers": { 61 | "mandatory": [ 62 | "left_command" 63 | ], 64 | "optional": [ 65 | "shift", 66 | "fn", 67 | "control", 68 | "right_command" 69 | ] 70 | } 71 | }, 72 | "to": [ 73 | { 74 | "key_code": "down_arrow" 75 | } 76 | ], 77 | "type": "basic" 78 | }, 79 | { 80 | "from": { 81 | "key_code": "k", 82 | "modifiers": { 83 | "mandatory": [ 84 | "left_command" 85 | ], 86 | "optional": [ 87 | "shift", 88 | "fn", 89 | "control", 90 | "right_command" 91 | ] 92 | } 93 | }, 94 | "to": [ 95 | { 96 | "key_code": "up_arrow" 97 | } 98 | ], 99 | "type": "basic" 100 | }, 101 | { 102 | "from": { 103 | "key_code": "l", 104 | "modifiers": { 105 | "mandatory": [ 106 | "left_command" 107 | ], 108 | "optional": [ 109 | "shift", 110 | "control", 111 | "right_command" 112 | ] 113 | } 114 | }, 115 | "to": [ 116 | { 117 | "key_code": "right_arrow" 118 | } 119 | ], 120 | "type": "basic" 121 | }, 122 | { 123 | "from": { 124 | "key_code": "h", 125 | "modifiers": { 126 | "mandatory": [ 127 | "left_option" 128 | ], 129 | "optional": [ 130 | "shift" 131 | ] 132 | } 133 | }, 134 | "to": [ 135 | { 136 | "key_code": "left_arrow", 137 | "modifiers": [ 138 | "left_option" 139 | ] 140 | } 141 | ], 142 | "type": "basic" 143 | }, 144 | { 145 | "from": { 146 | "key_code": "j", 147 | "modifiers": { 148 | "mandatory": [ 149 | "left_option" 150 | ], 151 | "optional": [ 152 | "shift" 153 | ] 154 | } 155 | }, 156 | "to": [ 157 | { 158 | "key_code": "down_arrow", 159 | "modifiers": [ 160 | "left_option" 161 | ] 162 | } 163 | ], 164 | "type": "basic" 165 | }, 166 | { 167 | "from": { 168 | "key_code": "k", 169 | "modifiers": { 170 | "mandatory": [ 171 | "left_option" 172 | ], 173 | "optional": [ 174 | "shift" 175 | ] 176 | } 177 | }, 178 | "to": [ 179 | { 180 | "key_code": "up_arrow", 181 | "modifiers": [ 182 | "left_option" 183 | ] 184 | } 185 | ], 186 | "type": "basic" 187 | }, 188 | { 189 | "from": { 190 | "key_code": "l", 191 | "modifiers": { 192 | "mandatory": [ 193 | "left_option" 194 | ], 195 | "optional": [ 196 | "shift" 197 | ] 198 | } 199 | }, 200 | "to": [ 201 | { 202 | "key_code": "right_arrow", 203 | "modifiers": [ 204 | "left_option" 205 | ] 206 | } 207 | ], 208 | "type": "basic" 209 | }, 210 | { 211 | "from": { 212 | "key_code": "h", 213 | "modifiers": { 214 | "mandatory": [ 215 | "control" 216 | ] 217 | } 218 | }, 219 | "to": [ 220 | { 221 | "key_code": "delete_or_backspace" 222 | } 223 | ], 224 | "type": "basic" 225 | }, 226 | { 227 | "from": { 228 | "key_code": "j", 229 | "modifiers": { 230 | "mandatory": [ 231 | "control" 232 | ] 233 | } 234 | }, 235 | "to": [ 236 | { 237 | "key_code": "vk_none" 238 | } 239 | ], 240 | "type": "basic" 241 | }, 242 | { 243 | "from": { 244 | "key_code": "k", 245 | "modifiers": { 246 | "mandatory": [ 247 | "control" 248 | ] 249 | } 250 | }, 251 | "to": [ 252 | { 253 | "key_code": "vk_none" 254 | } 255 | ], 256 | "type": "basic" 257 | }, 258 | { 259 | "from": { 260 | "key_code": "l", 261 | "modifiers": { 262 | "mandatory": [ 263 | "control" 264 | ] 265 | } 266 | }, 267 | "to": [ 268 | { 269 | "key_code": "delete_forward" 270 | } 271 | ], 272 | "type": "basic" 273 | }, 274 | { 275 | "from": { 276 | "key_code": "h", 277 | "modifiers": { 278 | "mandatory": [ 279 | "control", 280 | "left_option" 281 | ] 282 | } 283 | }, 284 | "to": [ 285 | { 286 | "key_code": "delete_or_backspace", 287 | "modifiers": [ 288 | "left_option" 289 | ] 290 | } 291 | ], 292 | "type": "basic" 293 | }, 294 | { 295 | "from": { 296 | "key_code": "j", 297 | "modifiers": { 298 | "mandatory": [ 299 | "control", 300 | "left_option" 301 | ] 302 | } 303 | }, 304 | "to": [ 305 | { 306 | "key_code": "vk_none" 307 | } 308 | ], 309 | "type": "basic" 310 | }, 311 | { 312 | "from": { 313 | "key_code": "k", 314 | "modifiers": { 315 | "mandatory": [ 316 | "control", 317 | "left_option" 318 | ] 319 | } 320 | }, 321 | "to": [ 322 | { 323 | "key_code": "vk_none" 324 | } 325 | ], 326 | "type": "basic" 327 | }, 328 | { 329 | "from": { 330 | "key_code": "l", 331 | "modifiers": { 332 | "mandatory": [ 333 | "control", 334 | "left_option" 335 | ] 336 | } 337 | }, 338 | "to": [ 339 | { 340 | "key_code": "delete_forward", 341 | "modifiers": [ 342 | "left_option" 343 | ] 344 | } 345 | ], 346 | "type": "basic" 347 | }, 348 | { 349 | "from": { 350 | "key_code": "h", 351 | "modifiers": { 352 | "mandatory": [ 353 | "fn" 354 | ], 355 | "optional": [ 356 | "command", 357 | "shift" 358 | ] 359 | } 360 | }, 361 | "to": [ 362 | { 363 | "key_code": "left_arrow", 364 | "modifiers": [ 365 | "command" 366 | ] 367 | } 368 | ], 369 | "type": "basic" 370 | }, 371 | { 372 | "from": { 373 | "key_code": "j", 374 | "modifiers": { 375 | "mandatory": [ 376 | "fn" 377 | ], 378 | "optional": [ 379 | "shift" 380 | ] 381 | } 382 | }, 383 | "to": [ 384 | { 385 | "key_code": "down_arrow", 386 | "modifiers": [ 387 | "fn" 388 | ] 389 | } 390 | ], 391 | "type": "basic" 392 | }, 393 | { 394 | "from": { 395 | "key_code": "k", 396 | "modifiers": { 397 | "mandatory": [ 398 | "fn" 399 | ], 400 | "optional": [ 401 | "shift" 402 | ] 403 | } 404 | }, 405 | "to": [ 406 | { 407 | "key_code": "up_arrow", 408 | "modifiers": [ 409 | "fn" 410 | ] 411 | } 412 | ], 413 | "type": "basic" 414 | }, 415 | { 416 | "from": { 417 | "key_code": "l", 418 | "modifiers": { 419 | "mandatory": [ 420 | "fn" 421 | ], 422 | "optional": [ 423 | "command", 424 | "shift" 425 | ] 426 | } 427 | }, 428 | "to": [ 429 | { 430 | "key_code": "right_arrow", 431 | "modifiers": [ 432 | "command" 433 | ] 434 | } 435 | ], 436 | "type": "basic" 437 | }, 438 | { 439 | "from": { 440 | "key_code": "h", 441 | "modifiers": { 442 | "mandatory": [ 443 | "control", 444 | "fn" 445 | ] 446 | } 447 | }, 448 | "to": [ 449 | { 450 | "key_code": "delete_or_backspace", 451 | "modifiers": [ 452 | "command" 453 | ] 454 | } 455 | ], 456 | "type": "basic" 457 | }, 458 | { 459 | "from": { 460 | "key_code": "l", 461 | "modifiers": { 462 | "mandatory": [ 463 | "control", 464 | "fn" 465 | ] 466 | } 467 | }, 468 | "to": [ 469 | { 470 | "key_code": "delete_or_backspace", 471 | "modifiers": [ 472 | "command", 473 | "fn" 474 | ] 475 | } 476 | ], 477 | "type": "basic" 478 | }, 479 | { 480 | "from": { 481 | "key_code": "open_bracket", 482 | "modifiers": { 483 | "mandatory": ["left_control"] 484 | } 485 | }, 486 | "to": [ 487 | { 488 | "key_code": "open_bracket", 489 | "modifiers": ["shift", "command"] 490 | } 491 | ], 492 | "type": "basic" 493 | }, 494 | { 495 | "from": { 496 | "key_code": "close_bracket", 497 | "modifiers": { 498 | "mandatory": ["left_control"] 499 | } 500 | }, 501 | "to": [ 502 | { 503 | "key_code": "close_bracket", 504 | "modifiers": ["shift", "command"] 505 | } 506 | ], 507 | "type": "basic" 508 | }, 509 | { 510 | "from": { 511 | "key_code": "tab", 512 | "modifiers": { 513 | "mandatory": ["left_control"] 514 | } 515 | }, 516 | "to": [ 517 | { 518 | "key_code": "close_bracket", 519 | "modifiers": ["shift", "command"] 520 | } 521 | ], 522 | "type": "basic" 523 | }, 524 | { 525 | "from": { 526 | "key_code": "tab", 527 | "modifiers": { 528 | "mandatory": ["left_control", "shift"] 529 | } 530 | }, 531 | "to": [ 532 | { 533 | "key_code": "open_bracket", 534 | "modifiers": ["shift", "command"] 535 | } 536 | ], 537 | "type": "basic" 538 | } 539 | ] 540 | } 541 | ] 542 | }, 543 | "devices": [ 544 | { 545 | "disable_built_in_keyboard_if_exists": false, 546 | "fn_function_keys": [], 547 | "identifiers": { 548 | "is_keyboard": true, 549 | "is_pointing_device": false, 550 | "product_id": 632, 551 | "vendor_id": 1452 552 | }, 553 | "ignore": false, 554 | "keyboard_type": 0, 555 | "manipulate_caps_lock_led": true, 556 | "simple_modifications": [] 557 | }, 558 | { 559 | "disable_built_in_keyboard_if_exists": false, 560 | "fn_function_keys": [], 561 | "identifiers": { 562 | "is_keyboard": true, 563 | "is_pointing_device": false, 564 | "product_id": 34304, 565 | "vendor_id": 1452 566 | }, 567 | "ignore": true, 568 | "keyboard_type": 0, 569 | "manipulate_caps_lock_led": true, 570 | "simple_modifications": [] 571 | }, 572 | { 573 | "disable_built_in_keyboard_if_exists": false, 574 | "fn_function_keys": [], 575 | "identifiers": { 576 | "is_keyboard": true, 577 | "is_pointing_device": false, 578 | "product_id": 91, 579 | "vendor_id": 5426 580 | }, 581 | "ignore": true, 582 | "manipulate_caps_lock_led": false, 583 | "simple_modifications": [] 584 | }, 585 | { 586 | "disable_built_in_keyboard_if_exists": false, 587 | "fn_function_keys": [], 588 | "identifiers": { 589 | "is_keyboard": true, 590 | "is_pointing_device": false, 591 | "product_id": 50489, 592 | "vendor_id": 1133 593 | }, 594 | "ignore": true, 595 | "manipulate_caps_lock_led": false, 596 | "simple_modifications": [] 597 | } 598 | ], 599 | "fn_function_keys": [ 600 | { 601 | "from": { 602 | "key_code": "f1" 603 | }, 604 | "to": { 605 | "key_code": "display_brightness_decrement" 606 | } 607 | }, 608 | { 609 | "from": { 610 | "key_code": "f2" 611 | }, 612 | "to": { 613 | "key_code": "display_brightness_increment" 614 | } 615 | }, 616 | { 617 | "from": { 618 | "key_code": "f3" 619 | }, 620 | "to": { 621 | "key_code": "mission_control" 622 | } 623 | }, 624 | { 625 | "from": { 626 | "key_code": "f4" 627 | }, 628 | "to": { 629 | "key_code": "launchpad" 630 | } 631 | }, 632 | { 633 | "from": { 634 | "key_code": "f5" 635 | }, 636 | "to": { 637 | "key_code": "illumination_decrement" 638 | } 639 | }, 640 | { 641 | "from": { 642 | "key_code": "f6" 643 | }, 644 | "to": { 645 | "key_code": "illumination_increment" 646 | } 647 | }, 648 | { 649 | "from": { 650 | "key_code": "f7" 651 | }, 652 | "to": { 653 | "key_code": "rewind" 654 | } 655 | }, 656 | { 657 | "from": { 658 | "key_code": "f8" 659 | }, 660 | "to": { 661 | "key_code": "play_or_pause" 662 | } 663 | }, 664 | { 665 | "from": { 666 | "key_code": "f9" 667 | }, 668 | "to": { 669 | "key_code": "fastforward" 670 | } 671 | }, 672 | { 673 | "from": { 674 | "key_code": "f10" 675 | }, 676 | "to": { 677 | "key_code": "mute" 678 | } 679 | }, 680 | { 681 | "from": { 682 | "key_code": "f11" 683 | }, 684 | "to": { 685 | "key_code": "volume_decrement" 686 | } 687 | }, 688 | { 689 | "from": { 690 | "key_code": "f12" 691 | }, 692 | "to": { 693 | "key_code": "volume_increment" 694 | } 695 | } 696 | ], 697 | "name": "Default profile", 698 | "selected": true, 699 | "simple_modifications": [ 700 | { 701 | "from": { 702 | "key_code": "caps_lock" 703 | }, 704 | "to": { 705 | "key_code": "left_control" 706 | } 707 | }, 708 | { 709 | "from": { 710 | "key_code": "left_control" 711 | }, 712 | "to": { 713 | "key_code": "f19" 714 | } 715 | } 716 | ], 717 | "virtual_hid_keyboard": { 718 | "caps_lock_delay_milliseconds": 0, 719 | "country_code": 0, 720 | "keyboard_type": "ansi" 721 | } 722 | } 723 | ] 724 | } 725 | -------------------------------------------------------------------------------- /testdata/inp_short_comments.json: -------------------------------------------------------------------------------- 1 | // First single line comment 2 | { 3 | "global": { 4 | "check_for_updates_on_startup": true, 5 | "show_in_menu_bar": false, 6 | "show_profile_name_in_menu_bar": false 7 | }, 8 | "profiles": [ 9 | { 10 | "complex_modifications": { 11 | "parameters": { 12 | /* First multi line comment on single line */ 13 | "basic.simultaneous_threshold_milliseconds": 50, 14 | "basic.to_delayed_action_delay_milliseconds": 500, 15 | "basic.to_if_alone_timeout_milliseconds": 1000, 16 | "basic.to_if_held_down_threshold_milliseconds": 500 17 | }, 18 | "rules": [ 19 | { 20 | "description": "Custom Tweaks", 21 | "manipulators": [ 22 | { 23 | "from": { 24 | "key_code": "f19", 25 | "modifiers": { 26 | "mandatory": [ 27 | // Second single line comment 28 | "left_command" 29 | ] 30 | } 31 | }, 32 | "to": [ 33 | { 34 | "key_code": "vk_none" 35 | } 36 | ], 37 | "type": "basic" 38 | } 39 | ] 40 | } 41 | ] 42 | } 43 | } 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /testdata/inp_short_nopunc.json: -------------------------------------------------------------------------------- 1 | { 2 | "global" { 3 | "check_for_updates_on_startup" true 4 | "show_in_menu_bar" false 5 | "show_profile_name_in_menu_bar" false 6 | } 7 | "profiles" [ 8 | { 9 | "complex_modifications" { 10 | "parameters" { 11 | "basic.simultaneous_threshold_milliseconds" 50 12 | "basic.to_delayed_action_delay_milliseconds" 500 13 | "basic.to_if_alone_timeout_milliseconds" 1000 14 | "basic.to_if_held_down_threshold_milliseconds" 500 15 | } 16 | "rules" [ 17 | { 18 | "description" "Custom Tweaks" 19 | "manipulators" [ 20 | { 21 | "from" { 22 | "key_code" "f19" 23 | "modifiers" { 24 | "mandatory" [ 25 | "left_command" 26 | ] 27 | } 28 | } 29 | "to" [ 30 | { 31 | "key_code" "vk_none" 32 | } 33 | ] 34 | "type" "basic" 35 | } 36 | ] 37 | } 38 | ] 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /testdata/inp_short_pure.json: -------------------------------------------------------------------------------- 1 | { 2 | "global": { 3 | "check_for_updates_on_startup": true, 4 | "show_in_menu_bar": false, 5 | "show_profile_name_in_menu_bar": false 6 | }, 7 | "profiles": [ 8 | { 9 | "complex_modifications": { 10 | "parameters": { 11 | "basic.simultaneous_threshold_milliseconds": 50, 12 | "basic.to_delayed_action_delay_milliseconds": 500, 13 | "basic.to_if_alone_timeout_milliseconds": 1000, 14 | "basic.to_if_held_down_threshold_milliseconds": 500 15 | }, 16 | "rules": [ 17 | { 18 | "description": "Custom Tweaks", 19 | "manipulators": [ 20 | { 21 | "from": { 22 | "key_code": "f19", 23 | "modifiers": { 24 | "mandatory": [ 25 | "left_command" 26 | ] 27 | } 28 | }, 29 | "to": [ 30 | { 31 | "key_code": "vk_none" 32 | } 33 | ], 34 | "type": "basic" 35 | } 36 | ] 37 | } 38 | ] 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /testdata/out_comment_block_multi_line.json: -------------------------------------------------------------------------------- 1 | /* block comment in JSON */ 2 | "valid JSON" 3 | -------------------------------------------------------------------------------- /testdata/out_comment_block_nested_multi_line.json: -------------------------------------------------------------------------------- 1 | /* block comment one */ 2 | [ 3 | /* block comment two */ 4 | { 5 | /* block comment three */ 6 | "four": "five" 7 | } 8 | ] 9 | -------------------------------------------------------------------------------- /testdata/out_lines.json: -------------------------------------------------------------------------------- 1 | {"ae02a7": "9552f6"} 2 | {"25fa37": "260ec9"} 3 | {"41a676": "9023a1"} 4 | {"9e5c7b": "2cb31f"} 5 | {"694bba": "d3cdb8"} 6 | {"27b475": "c1dcaa"} 7 | {"1f3247": "fddee8"} 8 | {"844234": "42a47a"} 9 | {"5102ba": "a7883d"} 10 | {"6dec88": "280666"} 11 | {"04e957": "658835"} 12 | {"ca2052": "2a1e1a"} 13 | {"8aae66": "9d11f2"} 14 | {"6c2e42": "451dc4"} 15 | {"619c21": "8498e2"} 16 | {"12c089": "4af0e7"} 17 | {"4500eb": "980361"} 18 | {"5bd20c": "54085d"} 19 | {"19412f": "11253d"} 20 | -------------------------------------------------------------------------------- /testdata/out_long_hybrid_commas.json: -------------------------------------------------------------------------------- 1 | { 2 | "global": { 3 | "check_for_updates_on_startup": true, 4 | "show_in_menu_bar": false, 5 | "show_profile_name_in_menu_bar": false, 6 | }, 7 | "profiles": [ 8 | { 9 | "complex_modifications": { 10 | "parameters": { 11 | "basic.simultaneous_threshold_milliseconds": 50, 12 | "basic.to_delayed_action_delay_milliseconds": 500, 13 | "basic.to_if_alone_timeout_milliseconds": 1000, 14 | "basic.to_if_held_down_threshold_milliseconds": 500, 15 | }, 16 | "rules": [ 17 | { 18 | "description": "Custom Tweaks", 19 | "manipulators": [ 20 | { 21 | "from": { 22 | "key_code": "f19", 23 | "modifiers": {"mandatory": ["left_command"]}, 24 | }, 25 | "to": [{"key_code": "vk_none"}], 26 | "type": "basic", 27 | }, 28 | { 29 | "from": { 30 | "key_code": "h", 31 | "modifiers": { 32 | "mandatory": ["left_command"], 33 | "optional": ["shift", "control", "right_command"], 34 | }, 35 | }, 36 | "to": [{"key_code": "left_arrow"}], 37 | "type": "basic", 38 | }, 39 | { 40 | "from": { 41 | "key_code": "j", 42 | "modifiers": { 43 | "mandatory": ["left_command"], 44 | "optional": ["shift", "fn", "control", "right_command"], 45 | }, 46 | }, 47 | "to": [{"key_code": "down_arrow"}], 48 | "type": "basic", 49 | }, 50 | { 51 | "from": { 52 | "key_code": "k", 53 | "modifiers": { 54 | "mandatory": ["left_command"], 55 | "optional": ["shift", "fn", "control", "right_command"], 56 | }, 57 | }, 58 | "to": [{"key_code": "up_arrow"}], 59 | "type": "basic", 60 | }, 61 | { 62 | "from": { 63 | "key_code": "l", 64 | "modifiers": { 65 | "mandatory": ["left_command"], 66 | "optional": ["shift", "control", "right_command"], 67 | }, 68 | }, 69 | "to": [{"key_code": "right_arrow"}], 70 | "type": "basic", 71 | }, 72 | { 73 | "from": { 74 | "key_code": "h", 75 | "modifiers": { 76 | "mandatory": ["left_option"], 77 | "optional": ["shift"], 78 | }, 79 | }, 80 | "to": [{"key_code": "left_arrow", "modifiers": ["left_option"]}], 81 | "type": "basic", 82 | }, 83 | { 84 | "from": { 85 | "key_code": "j", 86 | "modifiers": { 87 | "mandatory": ["left_option"], 88 | "optional": ["shift"], 89 | }, 90 | }, 91 | "to": [{"key_code": "down_arrow", "modifiers": ["left_option"]}], 92 | "type": "basic", 93 | }, 94 | { 95 | "from": { 96 | "key_code": "k", 97 | "modifiers": { 98 | "mandatory": ["left_option"], 99 | "optional": ["shift"], 100 | }, 101 | }, 102 | "to": [{"key_code": "up_arrow", "modifiers": ["left_option"]}], 103 | "type": "basic", 104 | }, 105 | { 106 | "from": { 107 | "key_code": "l", 108 | "modifiers": { 109 | "mandatory": ["left_option"], 110 | "optional": ["shift"], 111 | }, 112 | }, 113 | "to": [ 114 | {"key_code": "right_arrow", "modifiers": ["left_option"]}, 115 | ], 116 | "type": "basic", 117 | }, 118 | { 119 | "from": { 120 | "key_code": "h", 121 | "modifiers": {"mandatory": ["control"]}, 122 | }, 123 | "to": [{"key_code": "delete_or_backspace"}], 124 | "type": "basic", 125 | }, 126 | { 127 | "from": { 128 | "key_code": "j", 129 | "modifiers": {"mandatory": ["control"]}, 130 | }, 131 | "to": [{"key_code": "vk_none"}], 132 | "type": "basic", 133 | }, 134 | { 135 | "from": { 136 | "key_code": "k", 137 | "modifiers": {"mandatory": ["control"]}, 138 | }, 139 | "to": [{"key_code": "vk_none"}], 140 | "type": "basic", 141 | }, 142 | { 143 | "from": { 144 | "key_code": "l", 145 | "modifiers": {"mandatory": ["control"]}, 146 | }, 147 | "to": [{"key_code": "delete_forward"}], 148 | "type": "basic", 149 | }, 150 | { 151 | "from": { 152 | "key_code": "h", 153 | "modifiers": {"mandatory": ["control", "left_option"]}, 154 | }, 155 | "to": [ 156 | { 157 | "key_code": "delete_or_backspace", 158 | "modifiers": ["left_option"], 159 | }, 160 | ], 161 | "type": "basic", 162 | }, 163 | { 164 | "from": { 165 | "key_code": "j", 166 | "modifiers": {"mandatory": ["control", "left_option"]}, 167 | }, 168 | "to": [{"key_code": "vk_none"}], 169 | "type": "basic", 170 | }, 171 | { 172 | "from": { 173 | "key_code": "k", 174 | "modifiers": {"mandatory": ["control", "left_option"]}, 175 | }, 176 | "to": [{"key_code": "vk_none"}], 177 | "type": "basic", 178 | }, 179 | { 180 | "from": { 181 | "key_code": "l", 182 | "modifiers": {"mandatory": ["control", "left_option"]}, 183 | }, 184 | "to": [ 185 | {"key_code": "delete_forward", "modifiers": ["left_option"]}, 186 | ], 187 | "type": "basic", 188 | }, 189 | { 190 | "from": { 191 | "key_code": "h", 192 | "modifiers": { 193 | "mandatory": ["fn"], 194 | "optional": ["command", "shift"], 195 | }, 196 | }, 197 | "to": [{"key_code": "left_arrow", "modifiers": ["command"]}], 198 | "type": "basic", 199 | }, 200 | { 201 | "from": { 202 | "key_code": "j", 203 | "modifiers": {"mandatory": ["fn"], "optional": ["shift"]}, 204 | }, 205 | "to": [{"key_code": "down_arrow", "modifiers": ["fn"]}], 206 | "type": "basic", 207 | }, 208 | { 209 | "from": { 210 | "key_code": "k", 211 | "modifiers": {"mandatory": ["fn"], "optional": ["shift"]}, 212 | }, 213 | "to": [{"key_code": "up_arrow", "modifiers": ["fn"]}], 214 | "type": "basic", 215 | }, 216 | { 217 | "from": { 218 | "key_code": "l", 219 | "modifiers": { 220 | "mandatory": ["fn"], 221 | "optional": ["command", "shift"], 222 | }, 223 | }, 224 | "to": [{"key_code": "right_arrow", "modifiers": ["command"]}], 225 | "type": "basic", 226 | }, 227 | { 228 | "from": { 229 | "key_code": "h", 230 | "modifiers": {"mandatory": ["control", "fn"]}, 231 | }, 232 | "to": [ 233 | {"key_code": "delete_or_backspace", "modifiers": ["command"]}, 234 | ], 235 | "type": "basic", 236 | }, 237 | { 238 | "from": { 239 | "key_code": "l", 240 | "modifiers": {"mandatory": ["control", "fn"]}, 241 | }, 242 | "to": [ 243 | { 244 | "key_code": "delete_or_backspace", 245 | "modifiers": ["command", "fn"], 246 | }, 247 | ], 248 | "type": "basic", 249 | }, 250 | { 251 | "from": { 252 | "key_code": "open_bracket", 253 | "modifiers": {"mandatory": ["left_control"]}, 254 | }, 255 | "to": [ 256 | { 257 | "key_code": "open_bracket", 258 | "modifiers": ["shift", "command"], 259 | }, 260 | ], 261 | "type": "basic", 262 | }, 263 | { 264 | "from": { 265 | "key_code": "close_bracket", 266 | "modifiers": {"mandatory": ["left_control"]}, 267 | }, 268 | "to": [ 269 | { 270 | "key_code": "close_bracket", 271 | "modifiers": ["shift", "command"], 272 | }, 273 | ], 274 | "type": "basic", 275 | }, 276 | { 277 | "from": { 278 | "key_code": "tab", 279 | "modifiers": {"mandatory": ["left_control"]}, 280 | }, 281 | "to": [ 282 | { 283 | "key_code": "close_bracket", 284 | "modifiers": ["shift", "command"], 285 | }, 286 | ], 287 | "type": "basic", 288 | }, 289 | { 290 | "from": { 291 | "key_code": "tab", 292 | "modifiers": {"mandatory": ["left_control", "shift"]}, 293 | }, 294 | "to": [ 295 | { 296 | "key_code": "open_bracket", 297 | "modifiers": ["shift", "command"], 298 | }, 299 | ], 300 | "type": "basic", 301 | }, 302 | ], 303 | }, 304 | ], 305 | }, 306 | "devices": [ 307 | { 308 | "disable_built_in_keyboard_if_exists": false, 309 | "fn_function_keys": [], 310 | "identifiers": { 311 | "is_keyboard": true, 312 | "is_pointing_device": false, 313 | "product_id": 632, 314 | "vendor_id": 1452, 315 | }, 316 | "ignore": false, 317 | "keyboard_type": 0, 318 | "manipulate_caps_lock_led": true, 319 | "simple_modifications": [], 320 | }, 321 | { 322 | "disable_built_in_keyboard_if_exists": false, 323 | "fn_function_keys": [], 324 | "identifiers": { 325 | "is_keyboard": true, 326 | "is_pointing_device": false, 327 | "product_id": 34304, 328 | "vendor_id": 1452, 329 | }, 330 | "ignore": true, 331 | "keyboard_type": 0, 332 | "manipulate_caps_lock_led": true, 333 | "simple_modifications": [], 334 | }, 335 | { 336 | "disable_built_in_keyboard_if_exists": false, 337 | "fn_function_keys": [], 338 | "identifiers": { 339 | "is_keyboard": true, 340 | "is_pointing_device": false, 341 | "product_id": 91, 342 | "vendor_id": 5426, 343 | }, 344 | "ignore": true, 345 | "manipulate_caps_lock_led": false, 346 | "simple_modifications": [], 347 | }, 348 | { 349 | "disable_built_in_keyboard_if_exists": false, 350 | "fn_function_keys": [], 351 | "identifiers": { 352 | "is_keyboard": true, 353 | "is_pointing_device": false, 354 | "product_id": 50489, 355 | "vendor_id": 1133, 356 | }, 357 | "ignore": true, 358 | "manipulate_caps_lock_led": false, 359 | "simple_modifications": [], 360 | }, 361 | ], 362 | "fn_function_keys": [ 363 | { 364 | "from": {"key_code": "f1"}, 365 | "to": {"key_code": "display_brightness_decrement"}, 366 | }, 367 | { 368 | "from": {"key_code": "f2"}, 369 | "to": {"key_code": "display_brightness_increment"}, 370 | }, 371 | {"from": {"key_code": "f3"}, "to": {"key_code": "mission_control"}}, 372 | {"from": {"key_code": "f4"}, "to": {"key_code": "launchpad"}}, 373 | { 374 | "from": {"key_code": "f5"}, 375 | "to": {"key_code": "illumination_decrement"}, 376 | }, 377 | { 378 | "from": {"key_code": "f6"}, 379 | "to": {"key_code": "illumination_increment"}, 380 | }, 381 | {"from": {"key_code": "f7"}, "to": {"key_code": "rewind"}}, 382 | {"from": {"key_code": "f8"}, "to": {"key_code": "play_or_pause"}}, 383 | {"from": {"key_code": "f9"}, "to": {"key_code": "fastforward"}}, 384 | {"from": {"key_code": "f10"}, "to": {"key_code": "mute"}}, 385 | {"from": {"key_code": "f11"}, "to": {"key_code": "volume_decrement"}}, 386 | {"from": {"key_code": "f12"}, "to": {"key_code": "volume_increment"}}, 387 | ], 388 | "name": "Default profile", 389 | "selected": true, 390 | "simple_modifications": [ 391 | {"from": {"key_code": "caps_lock"}, "to": {"key_code": "left_control"}}, 392 | {"from": {"key_code": "left_control"}, "to": {"key_code": "f19"}}, 393 | ], 394 | "virtual_hid_keyboard": { 395 | "caps_lock_delay_milliseconds": 0, 396 | "country_code": 0, 397 | "keyboard_type": "ansi", 398 | }, 399 | }, 400 | ], 401 | } 402 | -------------------------------------------------------------------------------- /testdata/out_long_hybrid_commas_comments.json: -------------------------------------------------------------------------------- 1 | // First single line comment 2 | { 3 | "global": { 4 | "check_for_updates_on_startup": true, 5 | "show_in_menu_bar": false, 6 | "show_profile_name_in_menu_bar": false, 7 | }, 8 | "profiles": [ 9 | { 10 | "complex_modifications": { 11 | "parameters": { 12 | /* First multi line comment on single line */ 13 | "basic.simultaneous_threshold_milliseconds": 50, 14 | "basic.to_delayed_action_delay_milliseconds": 500, 15 | "basic.to_if_alone_timeout_milliseconds": 1000, 16 | "basic.to_if_held_down_threshold_milliseconds": 500, 17 | }, 18 | "rules": [ 19 | { 20 | "description": "Custom Tweaks", 21 | "manipulators": [ 22 | { 23 | "from": { 24 | "key_code": "f19", 25 | "modifiers": { 26 | "mandatory": [ 27 | // Second single line comment 28 | "left_command", 29 | ], 30 | }, 31 | }, 32 | "to": [ 33 | { 34 | "key_code": "vk_none", 35 | // Third single line comment 36 | }, 37 | ], 38 | "type": "basic", 39 | }, 40 | { 41 | "from": { 42 | "key_code": "h", 43 | "modifiers": { 44 | "mandatory": ["left_command"], 45 | "optional": ["shift", "control", "right_command"], 46 | }, 47 | }, 48 | "to": [{"key_code": "left_arrow"}], 49 | "type": "basic", 50 | }, 51 | { 52 | "from": { 53 | "key_code": "j", 54 | "modifiers": { 55 | "mandatory": ["left_command"], 56 | "optional": ["shift", "fn", "control", "right_command"], 57 | }, 58 | }, 59 | "to": [{"key_code": "down_arrow"}], 60 | "type": "basic", 61 | }, 62 | { 63 | "from": { 64 | "key_code": "k", 65 | "modifiers": { 66 | "mandatory": ["left_command"], 67 | "optional": ["shift", "fn", "control", "right_command"], 68 | }, 69 | }, 70 | "to": [{"key_code": "up_arrow"}], 71 | "type": "basic", 72 | }, 73 | { 74 | "from": { 75 | "key_code": "l", 76 | "modifiers": { 77 | "mandatory": ["left_command"], 78 | "optional": ["shift", "control", "right_command"], 79 | }, 80 | }, 81 | "to": [{"key_code": "right_arrow"}], 82 | "type": "basic", 83 | }, 84 | { 85 | "from": { 86 | "key_code": "h", 87 | "modifiers": { 88 | "mandatory": ["left_option"], 89 | "optional": ["shift"], 90 | }, 91 | }, 92 | "to": [{"key_code": "left_arrow", "modifiers": ["left_option"]}], 93 | "type": "basic", 94 | }, 95 | { 96 | "from": { 97 | "key_code": "j", 98 | "modifiers": { 99 | "mandatory": ["left_option"], 100 | "optional": ["shift"], 101 | }, 102 | }, 103 | "to": [{"key_code": "down_arrow", "modifiers": ["left_option"]}], 104 | "type": "basic", 105 | }, 106 | { 107 | "from": { 108 | "key_code": "k", 109 | "modifiers": { 110 | "mandatory": ["left_option"], 111 | "optional": ["shift"], 112 | }, 113 | }, 114 | "to": [{"key_code": "up_arrow", "modifiers": ["left_option"]}], 115 | "type": "basic", 116 | }, 117 | { 118 | "from": { 119 | "key_code": "l", 120 | "modifiers": { 121 | "mandatory": ["left_option"], 122 | "optional": ["shift"], 123 | }, 124 | }, 125 | "to": [ 126 | {"key_code": "right_arrow", "modifiers": ["left_option"]}, 127 | ], 128 | "type": "basic", 129 | }, 130 | { 131 | "from": { 132 | "key_code": "h", 133 | "modifiers": {"mandatory": ["control"]}, 134 | }, 135 | "to": [{"key_code": "delete_or_backspace"}], 136 | "type": "basic", 137 | }, 138 | { 139 | "from": { 140 | "key_code": "j", 141 | "modifiers": {"mandatory": ["control"]}, 142 | }, 143 | "to": [{"key_code": "vk_none"}], 144 | "type": "basic", 145 | }, 146 | { 147 | "from": { 148 | "key_code": "k", 149 | "modifiers": {"mandatory": ["control"]}, 150 | }, 151 | "to": [{"key_code": "vk_none"}], 152 | "type": "basic", 153 | }, 154 | { 155 | "from": { 156 | "key_code": "l", 157 | "modifiers": {"mandatory": ["control"]}, 158 | }, 159 | "to": [{"key_code": "delete_forward"}], 160 | "type": "basic", 161 | }, 162 | { 163 | "from": { 164 | "key_code": "h", 165 | "modifiers": {"mandatory": ["control", "left_option"]}, 166 | }, 167 | "to": [ 168 | { 169 | "key_code": "delete_or_backspace", 170 | "modifiers": ["left_option"], 171 | }, 172 | ], 173 | "type": "basic", 174 | }, 175 | { 176 | "from": { 177 | "key_code": "j", 178 | "modifiers": {"mandatory": ["control", "left_option"]}, 179 | }, 180 | "to": [{"key_code": "vk_none"}], 181 | "type": "basic", 182 | }, 183 | { 184 | "from": { 185 | "key_code": "k", 186 | "modifiers": {"mandatory": ["control", "left_option"]}, 187 | }, 188 | "to": [{"key_code": "vk_none"}], 189 | "type": "basic", 190 | }, 191 | { 192 | "from": { 193 | "key_code": "l", 194 | "modifiers": {"mandatory": ["control", "left_option"]}, 195 | }, 196 | "to": [ 197 | {"key_code": "delete_forward", "modifiers": ["left_option"]}, 198 | ], 199 | "type": "basic", 200 | }, 201 | { 202 | "from": { 203 | "key_code": "h", 204 | "modifiers": { 205 | "mandatory": ["fn"], 206 | "optional": ["command", "shift"], 207 | }, 208 | }, 209 | "to": [{"key_code": "left_arrow", "modifiers": ["command"]}], 210 | "type": "basic", 211 | }, 212 | { 213 | "from": { 214 | "key_code": "j", 215 | "modifiers": {"mandatory": ["fn"], "optional": ["shift"]}, 216 | }, 217 | "to": [{"key_code": "down_arrow", "modifiers": ["fn"]}], 218 | "type": "basic", 219 | }, 220 | { 221 | "from": { 222 | "key_code": "k", 223 | "modifiers": {"mandatory": ["fn"], "optional": ["shift"]}, 224 | }, 225 | "to": [{"key_code": "up_arrow", "modifiers": ["fn"]}], 226 | "type": "basic", 227 | }, 228 | { 229 | "from": { 230 | "key_code": "l", 231 | "modifiers": { 232 | "mandatory": ["fn"], 233 | "optional": ["command", "shift"], 234 | }, 235 | }, 236 | "to": [{"key_code": "right_arrow", "modifiers": ["command"]}], 237 | "type": "basic", 238 | }, 239 | { 240 | "from": { 241 | "key_code": "h", 242 | "modifiers": {"mandatory": ["control", "fn"]}, 243 | }, 244 | "to": [ 245 | {"key_code": "delete_or_backspace", "modifiers": ["command"]}, 246 | ], 247 | "type": "basic", 248 | }, 249 | { 250 | "from": { 251 | "key_code": "l", 252 | "modifiers": {"mandatory": ["control", "fn"]}, 253 | }, 254 | "to": [ 255 | { 256 | "key_code": "delete_or_backspace", 257 | "modifiers": ["command", "fn"], 258 | }, 259 | ], 260 | "type": "basic", 261 | }, 262 | { 263 | "from": { 264 | "key_code": "open_bracket", 265 | "modifiers": {"mandatory": ["left_control"]}, 266 | }, 267 | "to": [ 268 | { 269 | "key_code": "open_bracket", 270 | "modifiers": ["shift", "command"], 271 | }, 272 | ], 273 | "type": "basic", 274 | }, 275 | { 276 | "from": { 277 | "key_code": "close_bracket", 278 | "modifiers": {"mandatory": ["left_control"]}, 279 | }, 280 | "to": [ 281 | { 282 | "key_code": "close_bracket", 283 | "modifiers": ["shift", "command"], 284 | }, 285 | ], 286 | "type": "basic", 287 | }, 288 | { 289 | "from": { 290 | "key_code": "tab", 291 | "modifiers": {"mandatory": ["left_control"]}, 292 | }, 293 | "to": [ 294 | { 295 | "key_code": "close_bracket", 296 | "modifiers": ["shift", "command"], 297 | }, 298 | ], 299 | "type": "basic", 300 | }, 301 | { 302 | "from": { 303 | "key_code": "tab", 304 | "modifiers": {"mandatory": ["left_control", "shift"]}, 305 | }, 306 | "to": [ 307 | { 308 | "key_code": "open_bracket", 309 | "modifiers": ["shift", "command"], 310 | }, 311 | ], 312 | "type": "basic", 313 | }, 314 | ], 315 | }, 316 | ], 317 | }, 318 | "devices": [ 319 | { 320 | "disable_built_in_keyboard_if_exists": false, 321 | "fn_function_keys": [], 322 | "identifiers": { 323 | "is_keyboard": true, 324 | "is_pointing_device": false, 325 | "product_id": 632, 326 | "vendor_id": 1452, 327 | }, 328 | "ignore": false, 329 | "keyboard_type": 0, 330 | "manipulate_caps_lock_led": true, 331 | "simple_modifications": [], 332 | }, 333 | { 334 | "disable_built_in_keyboard_if_exists": false, 335 | "fn_function_keys": [], 336 | "identifiers": { 337 | "is_keyboard": true, 338 | "is_pointing_device": false, 339 | "product_id": 34304, 340 | "vendor_id": 1452, 341 | }, 342 | "ignore": true, 343 | "keyboard_type": 0, 344 | "manipulate_caps_lock_led": true, 345 | "simple_modifications": [], 346 | }, 347 | { 348 | "disable_built_in_keyboard_if_exists": false, 349 | "fn_function_keys": [], 350 | "identifiers": { 351 | "is_keyboard": true, 352 | "is_pointing_device": false, 353 | "product_id": 91, 354 | "vendor_id": 5426, 355 | }, 356 | "ignore": true, 357 | "manipulate_caps_lock_led": false, 358 | "simple_modifications": [], 359 | }, 360 | { 361 | "disable_built_in_keyboard_if_exists": false, 362 | "fn_function_keys": [], 363 | "identifiers": { 364 | "is_keyboard": true, 365 | "is_pointing_device": false, 366 | "product_id": 50489, 367 | "vendor_id": 1133, 368 | }, 369 | "ignore": true, 370 | "manipulate_caps_lock_led": false, 371 | "simple_modifications": [], 372 | }, 373 | ], 374 | "fn_function_keys": [ 375 | { 376 | "from": {"key_code": "f1"}, 377 | "to": {"key_code": "display_brightness_decrement"}, 378 | }, 379 | { 380 | "from": {"key_code": "f2"}, 381 | "to": {"key_code": "display_brightness_increment"}, 382 | }, 383 | {"from": {"key_code": "f3"}, "to": {"key_code": "mission_control"}}, 384 | {"from": {"key_code": "f4"}, "to": {"key_code": "launchpad"}}, 385 | { 386 | "from": {"key_code": "f5"}, 387 | "to": {"key_code": "illumination_decrement"}, 388 | }, 389 | { 390 | "from": {"key_code": "f6"}, 391 | "to": {"key_code": "illumination_increment"}, 392 | }, 393 | {"from": {"key_code": "f7"}, "to": {"key_code": "rewind"}}, 394 | {"from": {"key_code": "f8"}, "to": {"key_code": "play_or_pause"}}, 395 | {"from": {"key_code": "f9"}, "to": {"key_code": "fastforward"}}, 396 | {"from": {"key_code": "f10"}, "to": {"key_code": "mute"}}, 397 | {"from": {"key_code": "f11"}, "to": {"key_code": "volume_decrement"}}, 398 | {"from": {"key_code": "f12"}, "to": {"key_code": "volume_increment"}}, 399 | ], 400 | "name": "Default profile", 401 | "selected": true, 402 | "simple_modifications": [ 403 | {"from": {"key_code": "caps_lock"}, "to": {"key_code": "left_control"}}, 404 | {"from": {"key_code": "left_control"}, "to": {"key_code": "f19"}}, 405 | ], 406 | "virtual_hid_keyboard": { 407 | "caps_lock_delay_milliseconds": 0, 408 | "country_code": 0, 409 | "keyboard_type": "ansi", 410 | }, 411 | }, 412 | ], 413 | } 414 | -------------------------------------------------------------------------------- /testdata/out_long_multi.json: -------------------------------------------------------------------------------- 1 | { 2 | "global": { 3 | "check_for_updates_on_startup": true, 4 | "show_in_menu_bar": false, 5 | "show_profile_name_in_menu_bar": false 6 | }, 7 | "profiles": [ 8 | { 9 | "complex_modifications": { 10 | "parameters": { 11 | "basic.simultaneous_threshold_milliseconds": 50, 12 | "basic.to_delayed_action_delay_milliseconds": 500, 13 | "basic.to_if_alone_timeout_milliseconds": 1000, 14 | "basic.to_if_held_down_threshold_milliseconds": 500 15 | }, 16 | "rules": [ 17 | { 18 | "description": "Custom Tweaks", 19 | "manipulators": [ 20 | { 21 | "from": { 22 | "key_code": "f19", 23 | "modifiers": { 24 | "mandatory": [ 25 | "left_command" 26 | ] 27 | } 28 | }, 29 | "to": [ 30 | { 31 | "key_code": "vk_none" 32 | } 33 | ], 34 | "type": "basic" 35 | }, 36 | { 37 | "from": { 38 | "key_code": "h", 39 | "modifiers": { 40 | "mandatory": [ 41 | "left_command" 42 | ], 43 | "optional": [ 44 | "shift", 45 | "control", 46 | "right_command" 47 | ] 48 | } 49 | }, 50 | "to": [ 51 | { 52 | "key_code": "left_arrow" 53 | } 54 | ], 55 | "type": "basic" 56 | }, 57 | { 58 | "from": { 59 | "key_code": "j", 60 | "modifiers": { 61 | "mandatory": [ 62 | "left_command" 63 | ], 64 | "optional": [ 65 | "shift", 66 | "fn", 67 | "control", 68 | "right_command" 69 | ] 70 | } 71 | }, 72 | "to": [ 73 | { 74 | "key_code": "down_arrow" 75 | } 76 | ], 77 | "type": "basic" 78 | }, 79 | { 80 | "from": { 81 | "key_code": "k", 82 | "modifiers": { 83 | "mandatory": [ 84 | "left_command" 85 | ], 86 | "optional": [ 87 | "shift", 88 | "fn", 89 | "control", 90 | "right_command" 91 | ] 92 | } 93 | }, 94 | "to": [ 95 | { 96 | "key_code": "up_arrow" 97 | } 98 | ], 99 | "type": "basic" 100 | }, 101 | { 102 | "from": { 103 | "key_code": "l", 104 | "modifiers": { 105 | "mandatory": [ 106 | "left_command" 107 | ], 108 | "optional": [ 109 | "shift", 110 | "control", 111 | "right_command" 112 | ] 113 | } 114 | }, 115 | "to": [ 116 | { 117 | "key_code": "right_arrow" 118 | } 119 | ], 120 | "type": "basic" 121 | }, 122 | { 123 | "from": { 124 | "key_code": "h", 125 | "modifiers": { 126 | "mandatory": [ 127 | "left_option" 128 | ], 129 | "optional": [ 130 | "shift" 131 | ] 132 | } 133 | }, 134 | "to": [ 135 | { 136 | "key_code": "left_arrow", 137 | "modifiers": [ 138 | "left_option" 139 | ] 140 | } 141 | ], 142 | "type": "basic" 143 | }, 144 | { 145 | "from": { 146 | "key_code": "j", 147 | "modifiers": { 148 | "mandatory": [ 149 | "left_option" 150 | ], 151 | "optional": [ 152 | "shift" 153 | ] 154 | } 155 | }, 156 | "to": [ 157 | { 158 | "key_code": "down_arrow", 159 | "modifiers": [ 160 | "left_option" 161 | ] 162 | } 163 | ], 164 | "type": "basic" 165 | }, 166 | { 167 | "from": { 168 | "key_code": "k", 169 | "modifiers": { 170 | "mandatory": [ 171 | "left_option" 172 | ], 173 | "optional": [ 174 | "shift" 175 | ] 176 | } 177 | }, 178 | "to": [ 179 | { 180 | "key_code": "up_arrow", 181 | "modifiers": [ 182 | "left_option" 183 | ] 184 | } 185 | ], 186 | "type": "basic" 187 | }, 188 | { 189 | "from": { 190 | "key_code": "l", 191 | "modifiers": { 192 | "mandatory": [ 193 | "left_option" 194 | ], 195 | "optional": [ 196 | "shift" 197 | ] 198 | } 199 | }, 200 | "to": [ 201 | { 202 | "key_code": "right_arrow", 203 | "modifiers": [ 204 | "left_option" 205 | ] 206 | } 207 | ], 208 | "type": "basic" 209 | }, 210 | { 211 | "from": { 212 | "key_code": "h", 213 | "modifiers": { 214 | "mandatory": [ 215 | "control" 216 | ] 217 | } 218 | }, 219 | "to": [ 220 | { 221 | "key_code": "delete_or_backspace" 222 | } 223 | ], 224 | "type": "basic" 225 | }, 226 | { 227 | "from": { 228 | "key_code": "j", 229 | "modifiers": { 230 | "mandatory": [ 231 | "control" 232 | ] 233 | } 234 | }, 235 | "to": [ 236 | { 237 | "key_code": "vk_none" 238 | } 239 | ], 240 | "type": "basic" 241 | }, 242 | { 243 | "from": { 244 | "key_code": "k", 245 | "modifiers": { 246 | "mandatory": [ 247 | "control" 248 | ] 249 | } 250 | }, 251 | "to": [ 252 | { 253 | "key_code": "vk_none" 254 | } 255 | ], 256 | "type": "basic" 257 | }, 258 | { 259 | "from": { 260 | "key_code": "l", 261 | "modifiers": { 262 | "mandatory": [ 263 | "control" 264 | ] 265 | } 266 | }, 267 | "to": [ 268 | { 269 | "key_code": "delete_forward" 270 | } 271 | ], 272 | "type": "basic" 273 | }, 274 | { 275 | "from": { 276 | "key_code": "h", 277 | "modifiers": { 278 | "mandatory": [ 279 | "control", 280 | "left_option" 281 | ] 282 | } 283 | }, 284 | "to": [ 285 | { 286 | "key_code": "delete_or_backspace", 287 | "modifiers": [ 288 | "left_option" 289 | ] 290 | } 291 | ], 292 | "type": "basic" 293 | }, 294 | { 295 | "from": { 296 | "key_code": "j", 297 | "modifiers": { 298 | "mandatory": [ 299 | "control", 300 | "left_option" 301 | ] 302 | } 303 | }, 304 | "to": [ 305 | { 306 | "key_code": "vk_none" 307 | } 308 | ], 309 | "type": "basic" 310 | }, 311 | { 312 | "from": { 313 | "key_code": "k", 314 | "modifiers": { 315 | "mandatory": [ 316 | "control", 317 | "left_option" 318 | ] 319 | } 320 | }, 321 | "to": [ 322 | { 323 | "key_code": "vk_none" 324 | } 325 | ], 326 | "type": "basic" 327 | }, 328 | { 329 | "from": { 330 | "key_code": "l", 331 | "modifiers": { 332 | "mandatory": [ 333 | "control", 334 | "left_option" 335 | ] 336 | } 337 | }, 338 | "to": [ 339 | { 340 | "key_code": "delete_forward", 341 | "modifiers": [ 342 | "left_option" 343 | ] 344 | } 345 | ], 346 | "type": "basic" 347 | }, 348 | { 349 | "from": { 350 | "key_code": "h", 351 | "modifiers": { 352 | "mandatory": [ 353 | "fn" 354 | ], 355 | "optional": [ 356 | "command", 357 | "shift" 358 | ] 359 | } 360 | }, 361 | "to": [ 362 | { 363 | "key_code": "left_arrow", 364 | "modifiers": [ 365 | "command" 366 | ] 367 | } 368 | ], 369 | "type": "basic" 370 | }, 371 | { 372 | "from": { 373 | "key_code": "j", 374 | "modifiers": { 375 | "mandatory": [ 376 | "fn" 377 | ], 378 | "optional": [ 379 | "shift" 380 | ] 381 | } 382 | }, 383 | "to": [ 384 | { 385 | "key_code": "down_arrow", 386 | "modifiers": [ 387 | "fn" 388 | ] 389 | } 390 | ], 391 | "type": "basic" 392 | }, 393 | { 394 | "from": { 395 | "key_code": "k", 396 | "modifiers": { 397 | "mandatory": [ 398 | "fn" 399 | ], 400 | "optional": [ 401 | "shift" 402 | ] 403 | } 404 | }, 405 | "to": [ 406 | { 407 | "key_code": "up_arrow", 408 | "modifiers": [ 409 | "fn" 410 | ] 411 | } 412 | ], 413 | "type": "basic" 414 | }, 415 | { 416 | "from": { 417 | "key_code": "l", 418 | "modifiers": { 419 | "mandatory": [ 420 | "fn" 421 | ], 422 | "optional": [ 423 | "command", 424 | "shift" 425 | ] 426 | } 427 | }, 428 | "to": [ 429 | { 430 | "key_code": "right_arrow", 431 | "modifiers": [ 432 | "command" 433 | ] 434 | } 435 | ], 436 | "type": "basic" 437 | }, 438 | { 439 | "from": { 440 | "key_code": "h", 441 | "modifiers": { 442 | "mandatory": [ 443 | "control", 444 | "fn" 445 | ] 446 | } 447 | }, 448 | "to": [ 449 | { 450 | "key_code": "delete_or_backspace", 451 | "modifiers": [ 452 | "command" 453 | ] 454 | } 455 | ], 456 | "type": "basic" 457 | }, 458 | { 459 | "from": { 460 | "key_code": "l", 461 | "modifiers": { 462 | "mandatory": [ 463 | "control", 464 | "fn" 465 | ] 466 | } 467 | }, 468 | "to": [ 469 | { 470 | "key_code": "delete_or_backspace", 471 | "modifiers": [ 472 | "command", 473 | "fn" 474 | ] 475 | } 476 | ], 477 | "type": "basic" 478 | }, 479 | { 480 | "from": { 481 | "key_code": "open_bracket", 482 | "modifiers": { 483 | "mandatory": [ 484 | "left_control" 485 | ] 486 | } 487 | }, 488 | "to": [ 489 | { 490 | "key_code": "open_bracket", 491 | "modifiers": [ 492 | "shift", 493 | "command" 494 | ] 495 | } 496 | ], 497 | "type": "basic" 498 | }, 499 | { 500 | "from": { 501 | "key_code": "close_bracket", 502 | "modifiers": { 503 | "mandatory": [ 504 | "left_control" 505 | ] 506 | } 507 | }, 508 | "to": [ 509 | { 510 | "key_code": "close_bracket", 511 | "modifiers": [ 512 | "shift", 513 | "command" 514 | ] 515 | } 516 | ], 517 | "type": "basic" 518 | }, 519 | { 520 | "from": { 521 | "key_code": "tab", 522 | "modifiers": { 523 | "mandatory": [ 524 | "left_control" 525 | ] 526 | } 527 | }, 528 | "to": [ 529 | { 530 | "key_code": "close_bracket", 531 | "modifiers": [ 532 | "shift", 533 | "command" 534 | ] 535 | } 536 | ], 537 | "type": "basic" 538 | }, 539 | { 540 | "from": { 541 | "key_code": "tab", 542 | "modifiers": { 543 | "mandatory": [ 544 | "left_control", 545 | "shift" 546 | ] 547 | } 548 | }, 549 | "to": [ 550 | { 551 | "key_code": "open_bracket", 552 | "modifiers": [ 553 | "shift", 554 | "command" 555 | ] 556 | } 557 | ], 558 | "type": "basic" 559 | } 560 | ] 561 | } 562 | ] 563 | }, 564 | "devices": [ 565 | { 566 | "disable_built_in_keyboard_if_exists": false, 567 | "fn_function_keys": [], 568 | "identifiers": { 569 | "is_keyboard": true, 570 | "is_pointing_device": false, 571 | "product_id": 632, 572 | "vendor_id": 1452 573 | }, 574 | "ignore": false, 575 | "keyboard_type": 0, 576 | "manipulate_caps_lock_led": true, 577 | "simple_modifications": [] 578 | }, 579 | { 580 | "disable_built_in_keyboard_if_exists": false, 581 | "fn_function_keys": [], 582 | "identifiers": { 583 | "is_keyboard": true, 584 | "is_pointing_device": false, 585 | "product_id": 34304, 586 | "vendor_id": 1452 587 | }, 588 | "ignore": true, 589 | "keyboard_type": 0, 590 | "manipulate_caps_lock_led": true, 591 | "simple_modifications": [] 592 | }, 593 | { 594 | "disable_built_in_keyboard_if_exists": false, 595 | "fn_function_keys": [], 596 | "identifiers": { 597 | "is_keyboard": true, 598 | "is_pointing_device": false, 599 | "product_id": 91, 600 | "vendor_id": 5426 601 | }, 602 | "ignore": true, 603 | "manipulate_caps_lock_led": false, 604 | "simple_modifications": [] 605 | }, 606 | { 607 | "disable_built_in_keyboard_if_exists": false, 608 | "fn_function_keys": [], 609 | "identifiers": { 610 | "is_keyboard": true, 611 | "is_pointing_device": false, 612 | "product_id": 50489, 613 | "vendor_id": 1133 614 | }, 615 | "ignore": true, 616 | "manipulate_caps_lock_led": false, 617 | "simple_modifications": [] 618 | } 619 | ], 620 | "fn_function_keys": [ 621 | { 622 | "from": { 623 | "key_code": "f1" 624 | }, 625 | "to": { 626 | "key_code": "display_brightness_decrement" 627 | } 628 | }, 629 | { 630 | "from": { 631 | "key_code": "f2" 632 | }, 633 | "to": { 634 | "key_code": "display_brightness_increment" 635 | } 636 | }, 637 | { 638 | "from": { 639 | "key_code": "f3" 640 | }, 641 | "to": { 642 | "key_code": "mission_control" 643 | } 644 | }, 645 | { 646 | "from": { 647 | "key_code": "f4" 648 | }, 649 | "to": { 650 | "key_code": "launchpad" 651 | } 652 | }, 653 | { 654 | "from": { 655 | "key_code": "f5" 656 | }, 657 | "to": { 658 | "key_code": "illumination_decrement" 659 | } 660 | }, 661 | { 662 | "from": { 663 | "key_code": "f6" 664 | }, 665 | "to": { 666 | "key_code": "illumination_increment" 667 | } 668 | }, 669 | { 670 | "from": { 671 | "key_code": "f7" 672 | }, 673 | "to": { 674 | "key_code": "rewind" 675 | } 676 | }, 677 | { 678 | "from": { 679 | "key_code": "f8" 680 | }, 681 | "to": { 682 | "key_code": "play_or_pause" 683 | } 684 | }, 685 | { 686 | "from": { 687 | "key_code": "f9" 688 | }, 689 | "to": { 690 | "key_code": "fastforward" 691 | } 692 | }, 693 | { 694 | "from": { 695 | "key_code": "f10" 696 | }, 697 | "to": { 698 | "key_code": "mute" 699 | } 700 | }, 701 | { 702 | "from": { 703 | "key_code": "f11" 704 | }, 705 | "to": { 706 | "key_code": "volume_decrement" 707 | } 708 | }, 709 | { 710 | "from": { 711 | "key_code": "f12" 712 | }, 713 | "to": { 714 | "key_code": "volume_increment" 715 | } 716 | } 717 | ], 718 | "name": "Default profile", 719 | "selected": true, 720 | "simple_modifications": [ 721 | { 722 | "from": { 723 | "key_code": "caps_lock" 724 | }, 725 | "to": { 726 | "key_code": "left_control" 727 | } 728 | }, 729 | { 730 | "from": { 731 | "key_code": "left_control" 732 | }, 733 | "to": { 734 | "key_code": "f19" 735 | } 736 | } 737 | ], 738 | "virtual_hid_keyboard": { 739 | "caps_lock_delay_milliseconds": 0, 740 | "country_code": 0, 741 | "keyboard_type": "ansi" 742 | } 743 | } 744 | ] 745 | } 746 | -------------------------------------------------------------------------------- /testdata/out_long_single_comments.json: -------------------------------------------------------------------------------- 1 | // First single line comment 2 | {"global":{"check_for_updates_on_startup":true,"show_in_menu_bar":false,"show_profile_name_in_menu_bar":false},"profiles":[{"complex_modifications":{"parameters":{/* First multi line comment on single line */"basic.simultaneous_threshold_milliseconds":50,"basic.to_delayed_action_delay_milliseconds":500,"basic.to_if_alone_timeout_milliseconds":1000,"basic.to_if_held_down_threshold_milliseconds":500},"rules":[{"description":"Custom Tweaks","manipulators":[{"from":{"key_code":"f19","modifiers":{"mandatory":[// Second single line comment 3 | "left_command"]}},"to":[{"key_code":"vk_none"// Third single line comment 4 | }],"type":"basic"},{"from":{"key_code":"h","modifiers":{"mandatory":["left_command"],"optional":["shift","control","right_command"]}},"to":[{"key_code":"left_arrow"}],"type":"basic"},{"from":{"key_code":"j","modifiers":{"mandatory":["left_command"],"optional":["shift","fn","control","right_command"]}},"to":[{"key_code":"down_arrow"}],"type":"basic"},{"from":{"key_code":"k","modifiers":{"mandatory":["left_command"],"optional":["shift","fn","control","right_command"]}},"to":[{"key_code":"up_arrow"}],"type":"basic"},{"from":{"key_code":"l","modifiers":{"mandatory":["left_command"],"optional":["shift","control","right_command"]}},"to":[{"key_code":"right_arrow"}],"type":"basic"},{"from":{"key_code":"h","modifiers":{"mandatory":["left_option"],"optional":["shift"]}},"to":[{"key_code":"left_arrow","modifiers":["left_option"]}],"type":"basic"},{"from":{"key_code":"j","modifiers":{"mandatory":["left_option"],"optional":["shift"]}},"to":[{"key_code":"down_arrow","modifiers":["left_option"]}],"type":"basic"},{"from":{"key_code":"k","modifiers":{"mandatory":["left_option"],"optional":["shift"]}},"to":[{"key_code":"up_arrow","modifiers":["left_option"]}],"type":"basic"},{"from":{"key_code":"l","modifiers":{"mandatory":["left_option"],"optional":["shift"]}},"to":[{"key_code":"right_arrow","modifiers":["left_option"]}],"type":"basic"},{"from":{"key_code":"h","modifiers":{"mandatory":["control"]}},"to":[{"key_code":"delete_or_backspace"}],"type":"basic"},{"from":{"key_code":"j","modifiers":{"mandatory":["control"]}},"to":[{"key_code":"vk_none"}],"type":"basic"},{"from":{"key_code":"k","modifiers":{"mandatory":["control"]}},"to":[{"key_code":"vk_none"}],"type":"basic"},{"from":{"key_code":"l","modifiers":{"mandatory":["control"]}},"to":[{"key_code":"delete_forward"}],"type":"basic"},{"from":{"key_code":"h","modifiers":{"mandatory":["control","left_option"]}},"to":[{"key_code":"delete_or_backspace","modifiers":["left_option"]}],"type":"basic"},{"from":{"key_code":"j","modifiers":{"mandatory":["control","left_option"]}},"to":[{"key_code":"vk_none"}],"type":"basic"},{"from":{"key_code":"k","modifiers":{"mandatory":["control","left_option"]}},"to":[{"key_code":"vk_none"}],"type":"basic"},{"from":{"key_code":"l","modifiers":{"mandatory":["control","left_option"]}},"to":[{"key_code":"delete_forward","modifiers":["left_option"]}],"type":"basic"},{"from":{"key_code":"h","modifiers":{"mandatory":["fn"],"optional":["command","shift"]}},"to":[{"key_code":"left_arrow","modifiers":["command"]}],"type":"basic"},{"from":{"key_code":"j","modifiers":{"mandatory":["fn"],"optional":["shift"]}},"to":[{"key_code":"down_arrow","modifiers":["fn"]}],"type":"basic"},{"from":{"key_code":"k","modifiers":{"mandatory":["fn"],"optional":["shift"]}},"to":[{"key_code":"up_arrow","modifiers":["fn"]}],"type":"basic"},{"from":{"key_code":"l","modifiers":{"mandatory":["fn"],"optional":["command","shift"]}},"to":[{"key_code":"right_arrow","modifiers":["command"]}],"type":"basic"},{"from":{"key_code":"h","modifiers":{"mandatory":["control","fn"]}},"to":[{"key_code":"delete_or_backspace","modifiers":["command"]}],"type":"basic"},{"from":{"key_code":"l","modifiers":{"mandatory":["control","fn"]}},"to":[{"key_code":"delete_or_backspace","modifiers":["command","fn"]}],"type":"basic"},{"from":{"key_code":"open_bracket","modifiers":{"mandatory":["left_control"]}},"to":[{"key_code":"open_bracket","modifiers":["shift","command"]}],"type":"basic"},{"from":{"key_code":"close_bracket","modifiers":{"mandatory":["left_control"]}},"to":[{"key_code":"close_bracket","modifiers":["shift","command"]}],"type":"basic"},{"from":{"key_code":"tab","modifiers":{"mandatory":["left_control"]}},"to":[{"key_code":"close_bracket","modifiers":["shift","command"]}],"type":"basic"},{"from":{"key_code":"tab","modifiers":{"mandatory":["left_control","shift"]}},"to":[{"key_code":"open_bracket","modifiers":["shift","command"]}],"type":"basic"}]}]},"devices":[{"disable_built_in_keyboard_if_exists":false,"fn_function_keys":[],"identifiers":{"is_keyboard":true,"is_pointing_device":false,"product_id":632,"vendor_id":1452},"ignore":false,"keyboard_type":0,"manipulate_caps_lock_led":true,"simple_modifications":[]},{"disable_built_in_keyboard_if_exists":false,"fn_function_keys":[],"identifiers":{"is_keyboard":true,"is_pointing_device":false,"product_id":34304,"vendor_id":1452},"ignore":true,"keyboard_type":0,"manipulate_caps_lock_led":true,"simple_modifications":[]},{"disable_built_in_keyboard_if_exists":false,"fn_function_keys":[],"identifiers":{"is_keyboard":true,"is_pointing_device":false,"product_id":91,"vendor_id":5426},"ignore":true,"manipulate_caps_lock_led":false,"simple_modifications":[]},{"disable_built_in_keyboard_if_exists":false,"fn_function_keys":[],"identifiers":{"is_keyboard":true,"is_pointing_device":false,"product_id":50489,"vendor_id":1133},"ignore":true,"manipulate_caps_lock_led":false,"simple_modifications":[]}],"fn_function_keys":[{"from":{"key_code":"f1"},"to":{"key_code":"display_brightness_decrement"}},{"from":{"key_code":"f2"},"to":{"key_code":"display_brightness_increment"}},{"from":{"key_code":"f3"},"to":{"key_code":"mission_control"}},{"from":{"key_code":"f4"},"to":{"key_code":"launchpad"}},{"from":{"key_code":"f5"},"to":{"key_code":"illumination_decrement"}},{"from":{"key_code":"f6"},"to":{"key_code":"illumination_increment"}},{"from":{"key_code":"f7"},"to":{"key_code":"rewind"}},{"from":{"key_code":"f8"},"to":{"key_code":"play_or_pause"}},{"from":{"key_code":"f9"},"to":{"key_code":"fastforward"}},{"from":{"key_code":"f10"},"to":{"key_code":"mute"}},{"from":{"key_code":"f11"},"to":{"key_code":"volume_decrement"}},{"from":{"key_code":"f12"},"to":{"key_code":"volume_increment"}}],"name":"Default profile","selected":true,"simple_modifications":[{"from":{"key_code":"caps_lock"},"to":{"key_code":"left_control"}},{"from":{"key_code":"left_control"},"to":{"key_code":"f19"}}],"virtual_hid_keyboard":{"caps_lock_delay_milliseconds":0,"country_code":0,"keyboard_type":"ansi"}}]} -------------------------------------------------------------------------------- /testdata/out_long_single_stripped.json: -------------------------------------------------------------------------------- 1 | {"global":{"check_for_updates_on_startup":true,"show_in_menu_bar":false,"show_profile_name_in_menu_bar":false},"profiles":[{"complex_modifications":{"parameters":{"basic.simultaneous_threshold_milliseconds":50,"basic.to_delayed_action_delay_milliseconds":500,"basic.to_if_alone_timeout_milliseconds":1000,"basic.to_if_held_down_threshold_milliseconds":500},"rules":[{"description":"Custom Tweaks","manipulators":[{"from":{"key_code":"f19","modifiers":{"mandatory":["left_command"]}},"to":[{"key_code":"vk_none"}],"type":"basic"},{"from":{"key_code":"h","modifiers":{"mandatory":["left_command"],"optional":["shift","control","right_command"]}},"to":[{"key_code":"left_arrow"}],"type":"basic"},{"from":{"key_code":"j","modifiers":{"mandatory":["left_command"],"optional":["shift","fn","control","right_command"]}},"to":[{"key_code":"down_arrow"}],"type":"basic"},{"from":{"key_code":"k","modifiers":{"mandatory":["left_command"],"optional":["shift","fn","control","right_command"]}},"to":[{"key_code":"up_arrow"}],"type":"basic"},{"from":{"key_code":"l","modifiers":{"mandatory":["left_command"],"optional":["shift","control","right_command"]}},"to":[{"key_code":"right_arrow"}],"type":"basic"},{"from":{"key_code":"h","modifiers":{"mandatory":["left_option"],"optional":["shift"]}},"to":[{"key_code":"left_arrow","modifiers":["left_option"]}],"type":"basic"},{"from":{"key_code":"j","modifiers":{"mandatory":["left_option"],"optional":["shift"]}},"to":[{"key_code":"down_arrow","modifiers":["left_option"]}],"type":"basic"},{"from":{"key_code":"k","modifiers":{"mandatory":["left_option"],"optional":["shift"]}},"to":[{"key_code":"up_arrow","modifiers":["left_option"]}],"type":"basic"},{"from":{"key_code":"l","modifiers":{"mandatory":["left_option"],"optional":["shift"]}},"to":[{"key_code":"right_arrow","modifiers":["left_option"]}],"type":"basic"},{"from":{"key_code":"h","modifiers":{"mandatory":["control"]}},"to":[{"key_code":"delete_or_backspace"}],"type":"basic"},{"from":{"key_code":"j","modifiers":{"mandatory":["control"]}},"to":[{"key_code":"vk_none"}],"type":"basic"},{"from":{"key_code":"k","modifiers":{"mandatory":["control"]}},"to":[{"key_code":"vk_none"}],"type":"basic"},{"from":{"key_code":"l","modifiers":{"mandatory":["control"]}},"to":[{"key_code":"delete_forward"}],"type":"basic"},{"from":{"key_code":"h","modifiers":{"mandatory":["control","left_option"]}},"to":[{"key_code":"delete_or_backspace","modifiers":["left_option"]}],"type":"basic"},{"from":{"key_code":"j","modifiers":{"mandatory":["control","left_option"]}},"to":[{"key_code":"vk_none"}],"type":"basic"},{"from":{"key_code":"k","modifiers":{"mandatory":["control","left_option"]}},"to":[{"key_code":"vk_none"}],"type":"basic"},{"from":{"key_code":"l","modifiers":{"mandatory":["control","left_option"]}},"to":[{"key_code":"delete_forward","modifiers":["left_option"]}],"type":"basic"},{"from":{"key_code":"h","modifiers":{"mandatory":["fn"],"optional":["command","shift"]}},"to":[{"key_code":"left_arrow","modifiers":["command"]}],"type":"basic"},{"from":{"key_code":"j","modifiers":{"mandatory":["fn"],"optional":["shift"]}},"to":[{"key_code":"down_arrow","modifiers":["fn"]}],"type":"basic"},{"from":{"key_code":"k","modifiers":{"mandatory":["fn"],"optional":["shift"]}},"to":[{"key_code":"up_arrow","modifiers":["fn"]}],"type":"basic"},{"from":{"key_code":"l","modifiers":{"mandatory":["fn"],"optional":["command","shift"]}},"to":[{"key_code":"right_arrow","modifiers":["command"]}],"type":"basic"},{"from":{"key_code":"h","modifiers":{"mandatory":["control","fn"]}},"to":[{"key_code":"delete_or_backspace","modifiers":["command"]}],"type":"basic"},{"from":{"key_code":"l","modifiers":{"mandatory":["control","fn"]}},"to":[{"key_code":"delete_or_backspace","modifiers":["command","fn"]}],"type":"basic"},{"from":{"key_code":"open_bracket","modifiers":{"mandatory":["left_control"]}},"to":[{"key_code":"open_bracket","modifiers":["shift","command"]}],"type":"basic"},{"from":{"key_code":"close_bracket","modifiers":{"mandatory":["left_control"]}},"to":[{"key_code":"close_bracket","modifiers":["shift","command"]}],"type":"basic"},{"from":{"key_code":"tab","modifiers":{"mandatory":["left_control"]}},"to":[{"key_code":"close_bracket","modifiers":["shift","command"]}],"type":"basic"},{"from":{"key_code":"tab","modifiers":{"mandatory":["left_control","shift"]}},"to":[{"key_code":"open_bracket","modifiers":["shift","command"]}],"type":"basic"}]}]},"devices":[{"disable_built_in_keyboard_if_exists":false,"fn_function_keys":[],"identifiers":{"is_keyboard":true,"is_pointing_device":false,"product_id":632,"vendor_id":1452},"ignore":false,"keyboard_type":0,"manipulate_caps_lock_led":true,"simple_modifications":[]},{"disable_built_in_keyboard_if_exists":false,"fn_function_keys":[],"identifiers":{"is_keyboard":true,"is_pointing_device":false,"product_id":34304,"vendor_id":1452},"ignore":true,"keyboard_type":0,"manipulate_caps_lock_led":true,"simple_modifications":[]},{"disable_built_in_keyboard_if_exists":false,"fn_function_keys":[],"identifiers":{"is_keyboard":true,"is_pointing_device":false,"product_id":91,"vendor_id":5426},"ignore":true,"manipulate_caps_lock_led":false,"simple_modifications":[]},{"disable_built_in_keyboard_if_exists":false,"fn_function_keys":[],"identifiers":{"is_keyboard":true,"is_pointing_device":false,"product_id":50489,"vendor_id":1133},"ignore":true,"manipulate_caps_lock_led":false,"simple_modifications":[]}],"fn_function_keys":[{"from":{"key_code":"f1"},"to":{"key_code":"display_brightness_decrement"}},{"from":{"key_code":"f2"},"to":{"key_code":"display_brightness_increment"}},{"from":{"key_code":"f3"},"to":{"key_code":"mission_control"}},{"from":{"key_code":"f4"},"to":{"key_code":"launchpad"}},{"from":{"key_code":"f5"},"to":{"key_code":"illumination_decrement"}},{"from":{"key_code":"f6"},"to":{"key_code":"illumination_increment"}},{"from":{"key_code":"f7"},"to":{"key_code":"rewind"}},{"from":{"key_code":"f8"},"to":{"key_code":"play_or_pause"}},{"from":{"key_code":"f9"},"to":{"key_code":"fastforward"}},{"from":{"key_code":"f10"},"to":{"key_code":"mute"}},{"from":{"key_code":"f11"},"to":{"key_code":"volume_decrement"}},{"from":{"key_code":"f12"},"to":{"key_code":"volume_increment"}}],"name":"Default profile","selected":true,"simple_modifications":[{"from":{"key_code":"caps_lock"},"to":{"key_code":"left_control"}},{"from":{"key_code":"left_control"},"to":{"key_code":"f19"}}],"virtual_hid_keyboard":{"caps_lock_delay_milliseconds":0,"country_code":0,"keyboard_type":"ansi"}}]} -------------------------------------------------------------------------------- /testdata/out_short_punc.json: -------------------------------------------------------------------------------- 1 | { 2 | "global": { 3 | "check_for_updates_on_startup": true, 4 | "show_in_menu_bar": false, 5 | "show_profile_name_in_menu_bar": false, 6 | }, 7 | "profiles": [ 8 | { 9 | "complex_modifications": { 10 | "parameters": { 11 | "basic.simultaneous_threshold_milliseconds": 50, 12 | "basic.to_delayed_action_delay_milliseconds": 500, 13 | "basic.to_if_alone_timeout_milliseconds": 1000, 14 | "basic.to_if_held_down_threshold_milliseconds": 500, 15 | }, 16 | "rules": [ 17 | { 18 | "description": "Custom Tweaks", 19 | "manipulators": [ 20 | { 21 | "from": { 22 | "key_code": "f19", 23 | "modifiers": {"mandatory": ["left_command"]}, 24 | }, 25 | "to": [{"key_code": "vk_none"}], 26 | "type": "basic", 27 | }, 28 | ], 29 | }, 30 | ], 31 | }, 32 | }, 33 | ], 34 | } 35 | -------------------------------------------------------------------------------- /unlicense: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | --------------------------------------------------------------------------------