├── .gitignore ├── README.md ├── format.go ├── go.mod ├── go.sum ├── main.go └── release.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | target/ 3 | corgi 4 | 5 | # Created by https://www.toptal.com/developers/gitignore/api/osx,linux,windows,sublimetext,intellij,intellij+all,intellij+iml,go,golang,jetbrains,jetbrains+all,jetbrains+iml,svn,vim,webstorm,webstorm+all,webstorm+iml,zsh,goland,goland+all,goland+iml 6 | # Edit at https://www.toptal.com/developers/gitignore?templates=osx,linux,windows,sublimetext,intellij,intellij+all,intellij+iml,go,golang,jetbrains,jetbrains+all,jetbrains+iml,svn,vim,webstorm,webstorm+all,webstorm+iml,zsh,goland,goland+all,goland+iml 7 | 8 | ### Go ### 9 | # If you prefer the allow list template instead of the deny list, see community template: 10 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 11 | # 12 | # Binaries for programs and plugins 13 | *.exe 14 | *.exe~ 15 | *.dll 16 | *.so 17 | *.dylib 18 | 19 | # Test binary, built with `go test -c` 20 | *.test 21 | 22 | # Output of the go coverage tool, specifically when used with LiteIDE 23 | *.out 24 | 25 | # Dependency directories (remove the comment below to include it) 26 | # vendor/ 27 | 28 | # Go workspace file 29 | go.work 30 | 31 | ### GoLand ### 32 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 33 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 34 | 35 | # User-specific stuff 36 | .idea/**/workspace.xml 37 | .idea/**/tasks.xml 38 | .idea/**/usage.statistics.xml 39 | .idea/**/dictionaries 40 | .idea/**/shelf 41 | 42 | # AWS User-specific 43 | .idea/**/aws.xml 44 | 45 | # Generated files 46 | .idea/**/contentModel.xml 47 | 48 | # Sensitive or high-churn files 49 | .idea/**/dataSources/ 50 | .idea/**/dataSources.ids 51 | .idea/**/dataSources.local.xml 52 | .idea/**/sqlDataSources.xml 53 | .idea/**/dynamic.xml 54 | .idea/**/uiDesigner.xml 55 | .idea/**/dbnavigator.xml 56 | 57 | # Gradle 58 | .idea/**/gradle.xml 59 | .idea/**/libraries 60 | 61 | # Gradle and Maven with auto-import 62 | # When using Gradle or Maven with auto-import, you should exclude module files, 63 | # since they will be recreated, and may cause churn. Uncomment if using 64 | # auto-import. 65 | # .idea/artifacts 66 | # .idea/compiler.xml 67 | # .idea/jarRepositories.xml 68 | # .idea/modules.xml 69 | # .idea/*.iml 70 | # .idea/modules 71 | # *.iml 72 | # *.ipr 73 | 74 | # CMake 75 | cmake-build-*/ 76 | 77 | # Mongo Explorer plugin 78 | .idea/**/mongoSettings.xml 79 | 80 | # File-based project format 81 | *.iws 82 | 83 | # IntelliJ 84 | out/ 85 | 86 | # mpeltonen/sbt-idea plugin 87 | .idea_modules/ 88 | 89 | # JIRA plugin 90 | atlassian-ide-plugin.xml 91 | 92 | # Cursive Clojure plugin 93 | .idea/replstate.xml 94 | 95 | # SonarLint plugin 96 | .idea/sonarlint/ 97 | 98 | # Crashlytics plugin (for Android Studio and IntelliJ) 99 | com_crashlytics_export_strings.xml 100 | crashlytics.properties 101 | crashlytics-build.properties 102 | fabric.properties 103 | 104 | # Editor-based Rest Client 105 | .idea/httpRequests 106 | 107 | # Android studio 3.1+ serialized cache file 108 | .idea/caches/build_file_checksums.ser 109 | 110 | ### GoLand Patch ### 111 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 112 | 113 | # *.iml 114 | # modules.xml 115 | # .idea/misc.xml 116 | # *.ipr 117 | 118 | # Sonarlint plugin 119 | # https://plugins.jetbrains.com/plugin/7973-sonarlint 120 | .idea/**/sonarlint/ 121 | 122 | # SonarQube Plugin 123 | # https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin 124 | .idea/**/sonarIssues.xml 125 | 126 | # Markdown Navigator plugin 127 | # https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced 128 | .idea/**/markdown-navigator.xml 129 | .idea/**/markdown-navigator-enh.xml 130 | .idea/**/markdown-navigator/ 131 | 132 | # Cache file creation bug 133 | # See https://youtrack.jetbrains.com/issue/JBR-2257 134 | .idea/$CACHE_FILE$ 135 | 136 | # CodeStream plugin 137 | # https://plugins.jetbrains.com/plugin/12206-codestream 138 | .idea/codestream.xml 139 | 140 | # Azure Toolkit for IntelliJ plugin 141 | # https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij 142 | .idea/**/azureSettings.xml 143 | 144 | ### GoLand+all ### 145 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 146 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 147 | 148 | # User-specific stuff 149 | 150 | # AWS User-specific 151 | 152 | # Generated files 153 | 154 | # Sensitive or high-churn files 155 | 156 | # Gradle 157 | 158 | # Gradle and Maven with auto-import 159 | # When using Gradle or Maven with auto-import, you should exclude module files, 160 | # since they will be recreated, and may cause churn. Uncomment if using 161 | # auto-import. 162 | # .idea/artifacts 163 | # .idea/compiler.xml 164 | # .idea/jarRepositories.xml 165 | # .idea/modules.xml 166 | # .idea/*.iml 167 | # .idea/modules 168 | # *.iml 169 | # *.ipr 170 | 171 | # CMake 172 | 173 | # Mongo Explorer plugin 174 | 175 | # File-based project format 176 | 177 | # IntelliJ 178 | 179 | # mpeltonen/sbt-idea plugin 180 | 181 | # JIRA plugin 182 | 183 | # Cursive Clojure plugin 184 | 185 | # SonarLint plugin 186 | 187 | # Crashlytics plugin (for Android Studio and IntelliJ) 188 | 189 | # Editor-based Rest Client 190 | 191 | # Android studio 3.1+ serialized cache file 192 | 193 | ### GoLand+all Patch ### 194 | # Ignore everything but code style settings and run configurations 195 | # that are supposed to be shared within teams. 196 | 197 | .idea/* 198 | 199 | !.idea/codeStyles 200 | !.idea/runConfigurations 201 | 202 | ### GoLand+iml ### 203 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 204 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 205 | 206 | # User-specific stuff 207 | 208 | # AWS User-specific 209 | 210 | # Generated files 211 | 212 | # Sensitive or high-churn files 213 | 214 | # Gradle 215 | 216 | # Gradle and Maven with auto-import 217 | # When using Gradle or Maven with auto-import, you should exclude module files, 218 | # since they will be recreated, and may cause churn. Uncomment if using 219 | # auto-import. 220 | # .idea/artifacts 221 | # .idea/compiler.xml 222 | # .idea/jarRepositories.xml 223 | # .idea/modules.xml 224 | # .idea/*.iml 225 | # .idea/modules 226 | # *.iml 227 | # *.ipr 228 | 229 | # CMake 230 | 231 | # Mongo Explorer plugin 232 | 233 | # File-based project format 234 | 235 | # IntelliJ 236 | 237 | # mpeltonen/sbt-idea plugin 238 | 239 | # JIRA plugin 240 | 241 | # Cursive Clojure plugin 242 | 243 | # SonarLint plugin 244 | 245 | # Crashlytics plugin (for Android Studio and IntelliJ) 246 | 247 | # Editor-based Rest Client 248 | 249 | # Android studio 3.1+ serialized cache file 250 | 251 | ### GoLand+iml Patch ### 252 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 253 | 254 | *.iml 255 | modules.xml 256 | .idea/misc.xml 257 | *.ipr 258 | 259 | #!! ERROR: golang is undefined. Use list command to see defined gitignore types !!# 260 | 261 | ### Intellij ### 262 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 263 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 264 | 265 | # User-specific stuff 266 | 267 | # AWS User-specific 268 | 269 | # Generated files 270 | 271 | # Sensitive or high-churn files 272 | 273 | # Gradle 274 | 275 | # Gradle and Maven with auto-import 276 | # When using Gradle or Maven with auto-import, you should exclude module files, 277 | # since they will be recreated, and may cause churn. Uncomment if using 278 | # auto-import. 279 | # .idea/artifacts 280 | # .idea/compiler.xml 281 | # .idea/jarRepositories.xml 282 | # .idea/modules.xml 283 | # .idea/*.iml 284 | # .idea/modules 285 | # *.iml 286 | # *.ipr 287 | 288 | # CMake 289 | 290 | # Mongo Explorer plugin 291 | 292 | # File-based project format 293 | 294 | # IntelliJ 295 | 296 | # mpeltonen/sbt-idea plugin 297 | 298 | # JIRA plugin 299 | 300 | # Cursive Clojure plugin 301 | 302 | # SonarLint plugin 303 | 304 | # Crashlytics plugin (for Android Studio and IntelliJ) 305 | 306 | # Editor-based Rest Client 307 | 308 | # Android studio 3.1+ serialized cache file 309 | 310 | ### Intellij Patch ### 311 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 312 | 313 | # *.iml 314 | # modules.xml 315 | # .idea/misc.xml 316 | # *.ipr 317 | 318 | # Sonarlint plugin 319 | # https://plugins.jetbrains.com/plugin/7973-sonarlint 320 | 321 | # SonarQube Plugin 322 | # https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin 323 | 324 | # Markdown Navigator plugin 325 | # https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced 326 | 327 | # Cache file creation bug 328 | # See https://youtrack.jetbrains.com/issue/JBR-2257 329 | 330 | # CodeStream plugin 331 | # https://plugins.jetbrains.com/plugin/12206-codestream 332 | 333 | # Azure Toolkit for IntelliJ plugin 334 | # https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij 335 | 336 | ### Intellij+all ### 337 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 338 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 339 | 340 | # User-specific stuff 341 | 342 | # AWS User-specific 343 | 344 | # Generated files 345 | 346 | # Sensitive or high-churn files 347 | 348 | # Gradle 349 | 350 | # Gradle and Maven with auto-import 351 | # When using Gradle or Maven with auto-import, you should exclude module files, 352 | # since they will be recreated, and may cause churn. Uncomment if using 353 | # auto-import. 354 | # .idea/artifacts 355 | # .idea/compiler.xml 356 | # .idea/jarRepositories.xml 357 | # .idea/modules.xml 358 | # .idea/*.iml 359 | # .idea/modules 360 | # *.iml 361 | # *.ipr 362 | 363 | # CMake 364 | 365 | # Mongo Explorer plugin 366 | 367 | # File-based project format 368 | 369 | # IntelliJ 370 | 371 | # mpeltonen/sbt-idea plugin 372 | 373 | # JIRA plugin 374 | 375 | # Cursive Clojure plugin 376 | 377 | # SonarLint plugin 378 | 379 | # Crashlytics plugin (for Android Studio and IntelliJ) 380 | 381 | # Editor-based Rest Client 382 | 383 | # Android studio 3.1+ serialized cache file 384 | 385 | ### Intellij+all Patch ### 386 | # Ignore everything but code style settings and run configurations 387 | # that are supposed to be shared within teams. 388 | 389 | 390 | 391 | ### Intellij+iml ### 392 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 393 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 394 | 395 | # User-specific stuff 396 | 397 | # AWS User-specific 398 | 399 | # Generated files 400 | 401 | # Sensitive or high-churn files 402 | 403 | # Gradle 404 | 405 | # Gradle and Maven with auto-import 406 | # When using Gradle or Maven with auto-import, you should exclude module files, 407 | # since they will be recreated, and may cause churn. Uncomment if using 408 | # auto-import. 409 | # .idea/artifacts 410 | # .idea/compiler.xml 411 | # .idea/jarRepositories.xml 412 | # .idea/modules.xml 413 | # .idea/*.iml 414 | # .idea/modules 415 | # *.iml 416 | # *.ipr 417 | 418 | # CMake 419 | 420 | # Mongo Explorer plugin 421 | 422 | # File-based project format 423 | 424 | # IntelliJ 425 | 426 | # mpeltonen/sbt-idea plugin 427 | 428 | # JIRA plugin 429 | 430 | # Cursive Clojure plugin 431 | 432 | # SonarLint plugin 433 | 434 | # Crashlytics plugin (for Android Studio and IntelliJ) 435 | 436 | # Editor-based Rest Client 437 | 438 | # Android studio 3.1+ serialized cache file 439 | 440 | ### Intellij+iml Patch ### 441 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 442 | 443 | 444 | ### JetBrains ### 445 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 446 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 447 | 448 | # User-specific stuff 449 | 450 | # AWS User-specific 451 | 452 | # Generated files 453 | 454 | # Sensitive or high-churn files 455 | 456 | # Gradle 457 | 458 | # Gradle and Maven with auto-import 459 | # When using Gradle or Maven with auto-import, you should exclude module files, 460 | # since they will be recreated, and may cause churn. Uncomment if using 461 | # auto-import. 462 | # .idea/artifacts 463 | # .idea/compiler.xml 464 | # .idea/jarRepositories.xml 465 | # .idea/modules.xml 466 | # .idea/*.iml 467 | # .idea/modules 468 | # *.iml 469 | # *.ipr 470 | 471 | # CMake 472 | 473 | # Mongo Explorer plugin 474 | 475 | # File-based project format 476 | 477 | # IntelliJ 478 | 479 | # mpeltonen/sbt-idea plugin 480 | 481 | # JIRA plugin 482 | 483 | # Cursive Clojure plugin 484 | 485 | # SonarLint plugin 486 | 487 | # Crashlytics plugin (for Android Studio and IntelliJ) 488 | 489 | # Editor-based Rest Client 490 | 491 | # Android studio 3.1+ serialized cache file 492 | 493 | ### JetBrains Patch ### 494 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 495 | 496 | # *.iml 497 | # modules.xml 498 | # .idea/misc.xml 499 | # *.ipr 500 | 501 | # Sonarlint plugin 502 | # https://plugins.jetbrains.com/plugin/7973-sonarlint 503 | 504 | # SonarQube Plugin 505 | # https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin 506 | 507 | # Markdown Navigator plugin 508 | # https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced 509 | 510 | # Cache file creation bug 511 | # See https://youtrack.jetbrains.com/issue/JBR-2257 512 | 513 | # CodeStream plugin 514 | # https://plugins.jetbrains.com/plugin/12206-codestream 515 | 516 | # Azure Toolkit for IntelliJ plugin 517 | # https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij 518 | 519 | ### JetBrains+all ### 520 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 521 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 522 | 523 | # User-specific stuff 524 | 525 | # AWS User-specific 526 | 527 | # Generated files 528 | 529 | # Sensitive or high-churn files 530 | 531 | # Gradle 532 | 533 | # Gradle and Maven with auto-import 534 | # When using Gradle or Maven with auto-import, you should exclude module files, 535 | # since they will be recreated, and may cause churn. Uncomment if using 536 | # auto-import. 537 | # .idea/artifacts 538 | # .idea/compiler.xml 539 | # .idea/jarRepositories.xml 540 | # .idea/modules.xml 541 | # .idea/*.iml 542 | # .idea/modules 543 | # *.iml 544 | # *.ipr 545 | 546 | # CMake 547 | 548 | # Mongo Explorer plugin 549 | 550 | # File-based project format 551 | 552 | # IntelliJ 553 | 554 | # mpeltonen/sbt-idea plugin 555 | 556 | # JIRA plugin 557 | 558 | # Cursive Clojure plugin 559 | 560 | # SonarLint plugin 561 | 562 | # Crashlytics plugin (for Android Studio and IntelliJ) 563 | 564 | # Editor-based Rest Client 565 | 566 | # Android studio 3.1+ serialized cache file 567 | 568 | ### JetBrains+all Patch ### 569 | # Ignore everything but code style settings and run configurations 570 | # that are supposed to be shared within teams. 571 | 572 | 573 | 574 | ### JetBrains+iml ### 575 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 576 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 577 | 578 | # User-specific stuff 579 | 580 | # AWS User-specific 581 | 582 | # Generated files 583 | 584 | # Sensitive or high-churn files 585 | 586 | # Gradle 587 | 588 | # Gradle and Maven with auto-import 589 | # When using Gradle or Maven with auto-import, you should exclude module files, 590 | # since they will be recreated, and may cause churn. Uncomment if using 591 | # auto-import. 592 | # .idea/artifacts 593 | # .idea/compiler.xml 594 | # .idea/jarRepositories.xml 595 | # .idea/modules.xml 596 | # .idea/*.iml 597 | # .idea/modules 598 | # *.iml 599 | # *.ipr 600 | 601 | # CMake 602 | 603 | # Mongo Explorer plugin 604 | 605 | # File-based project format 606 | 607 | # IntelliJ 608 | 609 | # mpeltonen/sbt-idea plugin 610 | 611 | # JIRA plugin 612 | 613 | # Cursive Clojure plugin 614 | 615 | # SonarLint plugin 616 | 617 | # Crashlytics plugin (for Android Studio and IntelliJ) 618 | 619 | # Editor-based Rest Client 620 | 621 | # Android studio 3.1+ serialized cache file 622 | 623 | ### JetBrains+iml Patch ### 624 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 625 | 626 | 627 | ### Linux ### 628 | *~ 629 | 630 | # temporary files which can be created if a process still has a handle open of a deleted file 631 | .fuse_hidden* 632 | 633 | # KDE directory preferences 634 | .directory 635 | 636 | # Linux trash folder which might appear on any partition or disk 637 | .Trash-* 638 | 639 | # .nfs files are created when an open file is removed but is still being accessed 640 | .nfs* 641 | 642 | ### OSX ### 643 | # General 644 | .DS_Store 645 | .AppleDouble 646 | .LSOverride 647 | 648 | # Icon must end with two \r 649 | Icon 650 | 651 | 652 | # Thumbnails 653 | ._* 654 | 655 | # Files that might appear in the root of a volume 656 | .DocumentRevisions-V100 657 | .fseventsd 658 | .Spotlight-V100 659 | .TemporaryItems 660 | .Trashes 661 | .VolumeIcon.icns 662 | .com.apple.timemachine.donotpresent 663 | 664 | # Directories potentially created on remote AFP share 665 | .AppleDB 666 | .AppleDesktop 667 | Network Trash Folder 668 | Temporary Items 669 | .apdisk 670 | 671 | ### SublimeText ### 672 | # Cache files for Sublime Text 673 | *.tmlanguage.cache 674 | *.tmPreferences.cache 675 | *.stTheme.cache 676 | 677 | # Workspace files are user-specific 678 | *.sublime-workspace 679 | 680 | # Project files should be checked into the repository, unless a significant 681 | # proportion of contributors will probably not be using Sublime Text 682 | # *.sublime-project 683 | 684 | # SFTP configuration file 685 | sftp-config.json 686 | sftp-config-alt*.json 687 | 688 | # Package control specific files 689 | Package Control.last-run 690 | Package Control.ca-list 691 | Package Control.ca-bundle 692 | Package Control.system-ca-bundle 693 | Package Control.cache/ 694 | Package Control.ca-certs/ 695 | Package Control.merged-ca-bundle 696 | Package Control.user-ca-bundle 697 | oscrypto-ca-bundle.crt 698 | bh_unicode_properties.cache 699 | 700 | # Sublime-github package stores a github token in this file 701 | # https://packagecontrol.io/packages/sublime-github 702 | GitHub.sublime-settings 703 | 704 | ### SVN ### 705 | .svn/ 706 | 707 | ### Vim ### 708 | # Swap 709 | [._]*.s[a-v][a-z] 710 | !*.svg # comment out if you don't need vector files 711 | [._]*.sw[a-p] 712 | [._]s[a-rt-v][a-z] 713 | [._]ss[a-gi-z] 714 | [._]sw[a-p] 715 | 716 | # Session 717 | Session.vim 718 | Sessionx.vim 719 | 720 | # Temporary 721 | .netrwhist 722 | # Auto-generated tag files 723 | tags 724 | # Persistent undo 725 | [._]*.un~ 726 | 727 | ### WebStorm ### 728 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 729 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 730 | 731 | # User-specific stuff 732 | 733 | # AWS User-specific 734 | 735 | # Generated files 736 | 737 | # Sensitive or high-churn files 738 | 739 | # Gradle 740 | 741 | # Gradle and Maven with auto-import 742 | # When using Gradle or Maven with auto-import, you should exclude module files, 743 | # since they will be recreated, and may cause churn. Uncomment if using 744 | # auto-import. 745 | # .idea/artifacts 746 | # .idea/compiler.xml 747 | # .idea/jarRepositories.xml 748 | # .idea/modules.xml 749 | # .idea/*.iml 750 | # .idea/modules 751 | # *.iml 752 | # *.ipr 753 | 754 | # CMake 755 | 756 | # Mongo Explorer plugin 757 | 758 | # File-based project format 759 | 760 | # IntelliJ 761 | 762 | # mpeltonen/sbt-idea plugin 763 | 764 | # JIRA plugin 765 | 766 | # Cursive Clojure plugin 767 | 768 | # SonarLint plugin 769 | 770 | # Crashlytics plugin (for Android Studio and IntelliJ) 771 | 772 | # Editor-based Rest Client 773 | 774 | # Android studio 3.1+ serialized cache file 775 | 776 | ### WebStorm Patch ### 777 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 778 | 779 | # *.iml 780 | # modules.xml 781 | # .idea/misc.xml 782 | # *.ipr 783 | 784 | # Sonarlint plugin 785 | # https://plugins.jetbrains.com/plugin/7973-sonarlint 786 | 787 | # SonarQube Plugin 788 | # https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin 789 | 790 | # Markdown Navigator plugin 791 | # https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced 792 | 793 | # Cache file creation bug 794 | # See https://youtrack.jetbrains.com/issue/JBR-2257 795 | 796 | # CodeStream plugin 797 | # https://plugins.jetbrains.com/plugin/12206-codestream 798 | 799 | # Azure Toolkit for IntelliJ plugin 800 | # https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij 801 | 802 | ### WebStorm+all ### 803 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 804 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 805 | 806 | # User-specific stuff 807 | 808 | # AWS User-specific 809 | 810 | # Generated files 811 | 812 | # Sensitive or high-churn files 813 | 814 | # Gradle 815 | 816 | # Gradle and Maven with auto-import 817 | # When using Gradle or Maven with auto-import, you should exclude module files, 818 | # since they will be recreated, and may cause churn. Uncomment if using 819 | # auto-import. 820 | # .idea/artifacts 821 | # .idea/compiler.xml 822 | # .idea/jarRepositories.xml 823 | # .idea/modules.xml 824 | # .idea/*.iml 825 | # .idea/modules 826 | # *.iml 827 | # *.ipr 828 | 829 | # CMake 830 | 831 | # Mongo Explorer plugin 832 | 833 | # File-based project format 834 | 835 | # IntelliJ 836 | 837 | # mpeltonen/sbt-idea plugin 838 | 839 | # JIRA plugin 840 | 841 | # Cursive Clojure plugin 842 | 843 | # SonarLint plugin 844 | 845 | # Crashlytics plugin (for Android Studio and IntelliJ) 846 | 847 | # Editor-based Rest Client 848 | 849 | # Android studio 3.1+ serialized cache file 850 | 851 | ### WebStorm+all Patch ### 852 | # Ignore everything but code style settings and run configurations 853 | # that are supposed to be shared within teams. 854 | 855 | 856 | 857 | ### WebStorm+iml ### 858 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 859 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 860 | 861 | # User-specific stuff 862 | 863 | # AWS User-specific 864 | 865 | # Generated files 866 | 867 | # Sensitive or high-churn files 868 | 869 | # Gradle 870 | 871 | # Gradle and Maven with auto-import 872 | # When using Gradle or Maven with auto-import, you should exclude module files, 873 | # since they will be recreated, and may cause churn. Uncomment if using 874 | # auto-import. 875 | # .idea/artifacts 876 | # .idea/compiler.xml 877 | # .idea/jarRepositories.xml 878 | # .idea/modules.xml 879 | # .idea/*.iml 880 | # .idea/modules 881 | # *.iml 882 | # *.ipr 883 | 884 | # CMake 885 | 886 | # Mongo Explorer plugin 887 | 888 | # File-based project format 889 | 890 | # IntelliJ 891 | 892 | # mpeltonen/sbt-idea plugin 893 | 894 | # JIRA plugin 895 | 896 | # Cursive Clojure plugin 897 | 898 | # SonarLint plugin 899 | 900 | # Crashlytics plugin (for Android Studio and IntelliJ) 901 | 902 | # Editor-based Rest Client 903 | 904 | # Android studio 3.1+ serialized cache file 905 | 906 | ### WebStorm+iml Patch ### 907 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 908 | 909 | 910 | ### Windows ### 911 | # Windows thumbnail cache files 912 | Thumbs.db 913 | Thumbs.db:encryptable 914 | ehthumbs.db 915 | ehthumbs_vista.db 916 | 917 | # Dump file 918 | *.stackdump 919 | 920 | # Folder config file 921 | [Dd]esktop.ini 922 | 923 | # Recycle Bin used on file shares 924 | $RECYCLE.BIN/ 925 | 926 | # Windows Installer files 927 | *.cab 928 | *.msi 929 | *.msix 930 | *.msm 931 | *.msp 932 | 933 | # Windows shortcuts 934 | *.lnk 935 | 936 | ### Zsh ### 937 | # Zsh compiled script + zrecompile backup 938 | *.zwc 939 | *.zwc.old 940 | 941 | # Zsh completion-optimization dumpfile 942 | *zcompdump* 943 | 944 | # Zsh history 945 | .zsh_history 946 | 947 | # Zsh sessions 948 | .zsh_sessions 949 | 950 | # Zsh zcalc history 951 | .zcalc_history 952 | 953 | # A popular plugin manager's files 954 | ._zinit 955 | .zinit_lstupd 956 | 957 | # zdharma/zshelldoc tool's files 958 | zsdoc/data 959 | 960 | # robbyrussell/oh-my-zsh/plugins/per-directory-history plugin's files 961 | # (when set-up to store the history in the local directory) 962 | .directory_history 963 | 964 | # MichaelAquilina/zsh-autoswitch-virtualenv plugin's files 965 | # (for Zsh plugins using Python) 966 | .venv 967 | 968 | # Zunit tests' output 969 | /tests/_output/* 970 | !/tests/_output/.gitkeep 971 | 972 | # End of https://www.toptal.com/developers/gitignore/api/osx,linux,windows,sublimetext,intellij,intellij+all,intellij+iml,go,golang,jetbrains,jetbrains+all,jetbrains+iml,svn,vim,webstorm,webstorm+all,webstorm+iml,zsh,goland,goland+all,goland+iml 973 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Corgi - 打印 HTTP 请求 2 | 3 | ```shell 4 | $ ./corgi -h 5 | usage: corgi [-h|--help] [-p|--port ] [--max-printable-size ] 6 | [--pretty] [--fetch ""] 7 | 8 | Corgi HTTP Request Logger, version 1.1.0 9 | 10 | Arguments: 11 | 12 | -h --help Print help information 13 | -p --port 监听指定端口. Default: 8000 14 | --max-printable-size 请求体最大打印长度(0 15 | 表示不截断),JSON 和 URLEncoded 16 | 表单不受影响). Default: 256 17 | --pretty 特定类型请求体输出美化 18 | --fetch 转发请求到指定地址 19 | ``` 20 | 21 | 调试在线 HTTP Endpoint 可能需要为运行中的服务添加日志追踪点,重新打包和部署应用。 22 | Corgi 可以通过代理接口在不修改代码的情况下展示请求信息。 23 | 24 | 可能有一些同学已经用过这个 Z shell 函数了,函数会打印指定端口收到的 HTTP 请求并返回 `200 OK`,在调试时非常有用(, 25 | 缺点大概是还没用 Bash 重写,而且无限循环的退出机制也没有认真研究过)。Corgi 是这个想法的扩充。 26 | 27 | ```shell 28 | listen_port () { 29 | while true 30 | do 31 | { 32 | echo -e 'HTTP/1.1 200 OK\r\n' 33 | } | nc -l -v $1 34 | echo '\r\n' 35 | done 36 | } 37 | ``` 38 | 39 | - Corgi 可以美化输出 `application/json` 和 `application/x-www-form-urlencoded` 类型的请求体,后者会按行列出键值对,其中的值会被 URL 解码。 40 | 41 | ```shell 42 | $ ./corgi -p 8000 --pretty 43 | 44 | 2023/07/06 16:27:32 corgi is waiting on :8000 45 | 2023/07/06 16:27:38 POST /proxy?url=/iot/alipayApi/faceAuth/getAlipayUserInfo HTTP/1.1 46 | RemoteAddr: [::1]:57382 47 | Host: localhost:8000 48 | cookie: Cookie_1=value 49 | authorization: Bearer Igp5d444444444444444 50 | user-agent: PostmanRuntime/7.32.3 51 | accept: */* 52 | accept-encoding: gzip, deflate, br 53 | content-type: application/x-www-form-urlencoded 54 | content-length: 98 55 | postman-token: 9a00e0be-f921-4605-b2f3-b577c1e263c2 56 | connection: keep-alive 57 | 58 | payload={"username":"admin","password":"wecsnuigb43j@_f"} 59 | method=PATCH 60 | ``` 61 | 62 | - Corgi 可以把请求转发到目标地址,并且将目标地址的响应返回给客户端: 63 | 64 | ```shell 65 | $ ./corgi -p 8000 --fetch localhost:8001 --pretty 66 | 67 | 2023/07/07 16:37:40 POST /proxy?url=/iot/alipayApi/faceAuth/getAlipayUserInfo HTTP/1.1 68 | > RemoteAddr: [::1]:59214 69 | > Host: localhost:8000 70 | > user-agent: PostmanRuntime/7.32.3 71 | > content-type: application/x-www-form-urlencoded 72 | > cookie: Cookie_1=value 73 | > content-length: 98 74 | > authorization: Bearer Igp5d444444444444444 75 | > accept: */* 76 | > postman-token: 5dfc18b4-5902-4a15-94f5-53b23db764e7 77 | > accept-encoding: gzip, deflate, br 78 | > connection: keep-alive 79 | > 80 | > payload={"username":"admin","password":"wecsnuigb43j@_f"} 81 | > method=PATCH 82 | 83 | 2023/07/07 16:37:40 HTTP/1.1 200 200 OK 84 | < date: Fri, 07 Jul 2023 08:37:40 GMT 85 | < content-length: 9 86 | < content-type: text/plain; charset=utf-8 87 | < 88 | < woof woof 89 | ``` 90 | -------------------------------------------------------------------------------- /format.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "net/url" 10 | "strings" 11 | ) 12 | 13 | // 格式化 HTTP 请求 14 | func formatRequest(r *http.Request) []string { 15 | var request []string 16 | request = append(request, fmt.Sprintf("%v %v %v", r.Method, r.URL, r.Proto)) 17 | request = append(request, fmt.Sprintf("RemoteAddr: %v", r.RemoteAddr)) 18 | request = append(request, fmt.Sprintf("Host: %v", r.Host)) 19 | request = append(request, formatHeader(r.Header)...) 20 | if r.ContentLength > 0 { 21 | request = append(request, "") 22 | request = append(request, formatRequestBody(r, r.Header.Get("Content-Type"), prettyPrint)...) 23 | } 24 | return request 25 | } 26 | 27 | // 格式化 HTTP 响应 28 | func formatResponse(r *http.Response) []string { 29 | var response []string 30 | response = append(response, fmt.Sprintf("%v %v", r.Proto, r.Status)) 31 | response = append(response, formatHeader(r.Header)...) 32 | if r.ContentLength > 0 { 33 | response = append(response, "") 34 | response = append(response, prettyRaw(r.Body, int(r.ContentLength))...) 35 | } 36 | return response 37 | } 38 | 39 | // 格式化请求头 / 响应头 40 | func formatHeader(header http.Header) []string { 41 | var headers []string 42 | for name, values := range header { 43 | name = strings.ToLower(name) 44 | for _, value := range values { 45 | headers = append(headers, fmt.Sprintf("%v: %v", name, value)) 46 | } 47 | } 48 | return headers 49 | } 50 | 51 | // 格式化请求体,body 会被消耗,需要复用时在调取前先复制一份 52 | func formatRequestBody(r *http.Request, contentType string, pretty bool) []string { 53 | if !pretty { 54 | return prettyRaw(r.Body, int(r.ContentLength)) 55 | } 56 | switch contentType { 57 | case "application/json": 58 | return prettyJSON(r.Body) 59 | case "application/x-www-form-urlencoded": 60 | err := r.ParseForm() 61 | if err != nil { 62 | return []string{err.Error()} 63 | } else if prettyPrint { 64 | return prettyURLEncoded(r.PostForm) 65 | } else { 66 | return []string{r.PostForm.Encode()} 67 | } 68 | default: 69 | return prettyRaw(r.Body, int(r.ContentLength)) 70 | } 71 | } 72 | 73 | func prettyJSON(body io.ReadCloser) []string { 74 | b, err := io.ReadAll(body) 75 | if err != nil { 76 | return []string{err.Error()} 77 | } 78 | if b != nil { 79 | var prettyJSON bytes.Buffer 80 | error := json.Indent(&prettyJSON, b, "", " ") 81 | if error != nil { 82 | return []string{error.Error()} 83 | } 84 | return []string{prettyJSON.String()} 85 | } else { 86 | return []string{"[empty body]"} 87 | } 88 | } 89 | 90 | func prettyURLEncoded(form url.Values) []string { 91 | var kvpair []string 92 | for k, vs := range form { 93 | for _, v := range vs { 94 | unescaped, err := url.QueryUnescape(v) 95 | if err != nil { 96 | kvpair = append(kvpair, fmt.Sprintf("%v=%v", k, v)) 97 | } else { 98 | kvpair = append(kvpair, fmt.Sprintf("%v=%v", k, unescaped)) 99 | } 100 | } 101 | } 102 | return kvpair 103 | } 104 | 105 | func prettyRaw(body io.ReadCloser, contentLength int) []string { 106 | if maxPrintableBodySize == 0 || contentLength < maxPrintableBodySize { 107 | b, err := io.ReadAll(body) 108 | if err != nil { 109 | return []string{err.Error()} 110 | } else { 111 | return []string{string(b)} 112 | } 113 | } else { 114 | b := make([]byte, maxPrintableBodySize) 115 | _, err := io.ReadFull(body, b) 116 | if err != nil { 117 | return []string{err.Error()} 118 | } else { 119 | return []string{string(b), "[request body truncated...]"} 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module corgi 2 | 3 | go 1.20 4 | 5 | require github.com/akamensky/argparse v1.4.0 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/akamensky/argparse v1.4.0 h1:YGzvsTqCvbEZhL8zZu2AiA5nq805NZh75JNj4ajn1xc= 2 | github.com/akamensky/argparse v1.4.0/go.mod h1:S5kwC7IuDcEr5VeXtGPRVZ5o/FdhcMlQz4IZQuw64xA= 3 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "github.com/akamensky/argparse" 6 | "io" 7 | "log" 8 | "net/http" 9 | "os" 10 | "strconv" 11 | "strings" 12 | ) 13 | 14 | const ( 15 | Version = "1.1.1" 16 | DefaultPort = 8000 17 | DefaultMaxBodySize = 256 18 | ) 19 | 20 | var maxPrintableBodySize int 21 | var prettyPrint bool 22 | var fetchRemoteAddr string 23 | 24 | func main() { 25 | parser := argparse.NewParser("corgi", "Corgi HTTP Request Logger, version "+Version) 26 | listenOnPort := parser.Int("p", "port", &argparse.Options{Required: false, Default: DefaultPort, Help: "监听指定端口"}) 27 | maxBodySize := parser.Int("", "max-printable-size", &argparse.Options{Required: false, Default: DefaultMaxBodySize, Help: "请求体最大打印长度(0 表示不截断),JSON 和 URLEncoded 表单不受影响)"}) 28 | pretty := parser.Flag("", "pretty", &argparse.Options{Required: false, Help: "特定类型请求体输出美化"}) 29 | fetch := parser.String("", "fetch", &argparse.Options{Required: false, Help: "转发请求到指定地址"}) 30 | 31 | err := parser.Parse(os.Args) 32 | if err != nil { 33 | log.Fatal(parser.Usage(err)) 34 | } 35 | 36 | listenAddr := ":" + strconv.Itoa(*listenOnPort) 37 | log.Println("Corgi is waiting on " + strconv.Itoa(*listenOnPort)) 38 | maxPrintableBodySize = *maxBodySize 39 | prettyPrint = *pretty 40 | fetchRemoteAddr = *fetch 41 | 42 | var handler func(w http.ResponseWriter, r *http.Request) 43 | if fetchRemoteAddr != "" { 44 | log.Println("Corgi will grab and retrieve request to " + fetchRemoteAddr) 45 | handler = func(w http.ResponseWriter, originRequest *http.Request) { 46 | // 暂存请求体 47 | requestBodyBytes := makeRequestBodyReusable(originRequest) 48 | requestLog := formatRequest(originRequest) 49 | // 请求记录前添加方向符号 50 | log.Println(strings.Join(requestLog, "\n> ") + "\n") 51 | 52 | // 把请求转发到指定地址 53 | remoteResponse, err := throwOutRequest(originRequest, requestBodyBytes) 54 | var responseLog []string 55 | // 暂存响应体 56 | responseBodyBytes := makeResponseBodyReusable(remoteResponse) 57 | if err != nil { 58 | responseLog = []string{err.Error()} 59 | } else { 60 | responseLog = formatResponse(remoteResponse) 61 | } 62 | // 响应记录前添加方向符号 63 | log.Println(strings.Join(responseLog, "\n< ") + "\n") 64 | // 把响应转发给原始请求者 65 | w.WriteHeader(remoteResponse.StatusCode) 66 | for name, values := range remoteResponse.Header { 67 | for _, value := range values { 68 | w.Header().Add(name, value) 69 | } 70 | } 71 | _, _ = w.Write(responseBodyBytes) 72 | } 73 | } else { 74 | handler = func(w http.ResponseWriter, r *http.Request) { 75 | log.Println(strings.Join(formatRequest(r), "\n") + "\n") 76 | w.WriteHeader(http.StatusOK) 77 | _, _ = w.Write([]byte("woof woof")) 78 | } 79 | } 80 | err = http.ListenAndServe(listenAddr, http.HandlerFunc(handler)) 81 | if err != nil { 82 | log.Fatal(err) 83 | return 84 | } 85 | } 86 | 87 | func makeRequestBodyReusable(r *http.Request) []byte { 88 | bodyBytes, _ := io.ReadAll(r.Body) 89 | r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) 90 | return bodyBytes 91 | } 92 | 93 | func makeResponseBodyReusable(r *http.Response) []byte { 94 | bodyBytes, _ := io.ReadAll(r.Body) 95 | r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) 96 | return bodyBytes 97 | } 98 | 99 | func throwOutRequest(r *http.Request, body []byte) (*http.Response, error) { 100 | cli := &http.Client{} 101 | newRequest, err := http.NewRequest(r.Method, r.URL.String(), io.NopCloser(bytes.NewBuffer(body))) 102 | if err != nil { 103 | return nil, err 104 | } 105 | newRequest.Header = r.Header 106 | newRequest.URL.Host = fetchRemoteAddr 107 | newRequest.URL.Scheme = "http" 108 | // TODO 有时候不能正确计算 Content-Length? 109 | newRequest.ContentLength = int64(len(body)) 110 | response, err := cli.Do(newRequest) 111 | if err != nil { 112 | return nil, err 113 | } 114 | return response, nil 115 | } 116 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | rm -rf target && mkdir target 3 | 4 | # build for windows-x86_64 5 | CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o target/corgi-windows-amd64.exe 6 | # build for macOS-x86_64 7 | CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o target/corgi-darwin-amd64 8 | #build for macOS-arm64 9 | CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -o target/corgi-darwin-arm64 10 | # build for linux-x86_64 11 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o target/corgi-linux-amd64 12 | 13 | # calculate sha256 checksum 14 | cd target 15 | echo "sha256 checksum" > checksum 16 | sha256sum corgi-windows-amd64.exe >> checksum 17 | sha256sum corgi-darwin-amd64 >> checksum 18 | sha256sum corgi-darwin-arm64 >> checksum 19 | sha256sum corgi-linux-amd64 >> checksum 20 | --------------------------------------------------------------------------------