├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ └── release.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE.md ├── Makefile ├── README.md ├── _examples ├── example-doc.md ├── example.json └── example_env.json ├── assets ├── bootstrap.min.css ├── bootstrap.min.js ├── github-markdown.min.css ├── index.html ├── index.md ├── jquery.min.js ├── markdown.html ├── scripts.js └── styles.css ├── assets_bin └── assets.go ├── build.sh ├── cmd ├── build-html.go ├── build-markdown.go ├── build.go ├── funcmap.go ├── root.go ├── server.go └── version.go ├── collection ├── collection.go ├── collection_test.go └── env.go ├── generate-asset.go ├── go.mod ├── go.sum ├── install.sh ├── main.go ├── screenshot.png ├── uninstall.sh └── update ├── github.go └── update.go /.gitattributes: -------------------------------------------------------------------------------- 1 | assets/* linguist-vendored 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gomod" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | target-branch: "v3" 8 | # Labels on pull requests for version updates only 9 | labels: 10 | - "go mod dependencies" -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v[0-9]+.*" 7 | 8 | jobs: 9 | build_release: 10 | name: build_release 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: checkout and setup 14 | uses: actions/checkout@v2 15 | with: 16 | fetch-depth: 0 17 | - name: update Golang 18 | uses: actions/setup-go@v2 19 | with: 20 | go-version: '1.17.2' 21 | - name: build binary files 22 | run: make binary 23 | - name: list directory 24 | run: ls -la && ls -la bin 25 | - name: release 26 | uses: actions/create-release@v1 27 | id: create_release 28 | with: 29 | draft: false 30 | prerelease: false 31 | release_name: ${{ github.ref }} 32 | tag_name: ${{ github.ref }} 33 | body_path: CHANGELOG.md 34 | env: 35 | GITHUB_TOKEN: ${{ github.token }} 36 | - name: upload darwin_amd64 binary 37 | uses: actions/upload-release-asset@v1 38 | env: 39 | GITHUB_TOKEN: ${{ github.token }} 40 | with: 41 | upload_url: ${{ steps.create_release.outputs.upload_url }} 42 | asset_path: bin/darwin_amd64 43 | asset_name: darwin_amd64 44 | asset_content_type: application/octet-stream 45 | - name: upload darwin_arm64 binary 46 | uses: actions/upload-release-asset@v1 47 | env: 48 | GITHUB_TOKEN: ${{ github.token }} 49 | with: 50 | upload_url: ${{ steps.create_release.outputs.upload_url }} 51 | asset_path: bin/darwin_arm64 52 | asset_name: darwin_arm64 53 | asset_content_type: application/octet-stream 54 | - name: upload linux_amd64 binary 55 | uses: actions/upload-release-asset@v1 56 | env: 57 | GITHUB_TOKEN: ${{ github.token }} 58 | with: 59 | upload_url: ${{ steps.create_release.outputs.upload_url }} 60 | asset_path: bin/linux_amd64 61 | asset_name: linux_amd64 62 | asset_content_type: application/octet-stream 63 | - name: upload linux_arm64 binary 64 | uses: actions/upload-release-asset@v1 65 | env: 66 | GITHUB_TOKEN: ${{ github.token }} 67 | with: 68 | upload_url: ${{ steps.create_release.outputs.upload_url }} 69 | asset_path: bin/linux_arm64 70 | asset_name: linux_arm64 71 | asset_content_type: application/octet-stream 72 | - name: upload linux_386 binary 73 | uses: actions/upload-release-asset@v1 74 | env: 75 | GITHUB_TOKEN: ${{ github.token }} 76 | with: 77 | upload_url: ${{ steps.create_release.outputs.upload_url }} 78 | asset_path: bin/linux_386 79 | asset_name: linux_386 80 | asset_content_type: application/octet-stream 81 | - name: upload windows_386 binary 82 | uses: actions/upload-release-asset@v1 83 | env: 84 | GITHUB_TOKEN: ${{ github.token }} 85 | with: 86 | upload_url: ${{ steps.create_release.outputs.upload_url }} 87 | asset_path: bin/windows_386.exe 88 | asset_name: windows_386.exe 89 | asset_content_type: application/octet-stream 90 | - name: upload windows_amd64 binary 91 | uses: actions/upload-release-asset@v1 92 | env: 93 | GITHUB_TOKEN: ${{ github.token }} 94 | with: 95 | upload_url: ${{ steps.create_release.outputs.upload_url }} 96 | asset_path: bin/windows_amd64.exe 97 | asset_name: windows_amd64.exe 98 | asset_content_type: application/octet-stream -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | *.pdf 3 | docgen 4 | vendor 5 | .idea 6 | bin/linux_386 7 | bin/linux_amd64 8 | bin/linux_arm64 9 | bin/darwin_amd64 10 | bin/darwin_arm64 11 | bin/windows_386.exe 12 | bin/windows_amd64.exe -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | * Implement auto update binary (docgen) 2 | * Display `None` for empty request/response body 3 | * Change request/response body from H5->H4 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Must follow the guide for issues 4 | - Use the search tool before opening a new issue. 5 | - Please provide source code and stack trace if you found a bug. 6 | - Please review the existing issues and provide feedback to them 7 | 8 | ## Pull Request Process 9 | - Open your pull request against `dev` branch 10 | - It should pass all tests in the available continuous integrations systems such as TravisCI. 11 | - You should add/modify tests to cover your proposed code changes. 12 | - If your pull request contains a new feature, please document it on the README. 13 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.16.3-alpine3.13 as build 2 | 3 | WORKDIR /build 4 | 5 | ENV GO111MODULE=on 6 | ENV CGO_ENABLED=0 7 | 8 | RUN apk update && \ 9 | apk upgrade && \ 10 | apk add git 11 | 12 | RUN git clone https://github.com/thedevsaddam/docgen.git . 13 | 14 | RUN go generate 15 | RUN go build -o main . 16 | RUN chmod +x main 17 | 18 | FROM alpine:3.13 19 | 20 | COPY --from=build /build/main /usr/local/bin/docgen 21 | 22 | RUN mkdir /export 23 | VOLUME "/export" 24 | WORKDIR "/export" 25 | 26 | ENTRYPOINT [ "docgen", "build", "-i", "postman_collection.json" ] -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Saddam H 4 | 5 | > Permission is hereby granted, free of charge, to any person obtaining a copy 6 | > of this software and associated documentation files (the "Software"), to deal 7 | > in the Software without restriction, including without limitation the rights 8 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | > copies of the Software, and to permit persons to whom the Software is 10 | > furnished to do so, subject to the following conditions: 11 | > 12 | > The above copyright notice and this permission notice shall be included in 13 | > all copies or substantial portions of the Software. 14 | > 15 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | > THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all test coverage 2 | all: test build 3 | build: 4 | go build -o docgen 5 | binary: 6 | go run generate-asset.go 7 | ./build.sh 8 | install: 9 | go install ./... 10 | test: 11 | go test ./... -v -coverprofile .coverage.txt 12 | go tool cover -func .coverage.txt 13 | coverage: test 14 | go tool cover -html=.coverage.txt -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Docgen 2 | ===================== 3 | 4 | Transform your postman collection to HTML/Markdown documentation 5 | 6 | ![Task screenshot](screenshot.png) 7 | 8 | #### Installation/Update on Mac/Linux 9 | ```bash 10 | curl https://raw.githubusercontent.com/thedevsaddam/docgen/v3/install.sh -o install.sh \ 11 | && sudo chmod +x install.sh \ 12 | && sudo ./install.sh \ 13 | && rm install.sh 14 | ``` 15 | 16 | #### Uninstallation 17 | ```bash 18 | curl https://raw.githubusercontent.com/thedevsaddam/docgen/v3/uninstall.sh -o uninstall.sh \ 19 | && sudo chmod +x uninstall.sh \ 20 | && sudo ./uninstall.sh \ 21 | && rm uninstall.sh 22 | ``` 23 | 24 | #### Windows 25 | **For windows download the binary and set environment variable so that you can access the binary from terminal** 26 | 27 | #### Binary link 28 | [Download binary](https://github.com/thedevsaddam/docgen/releases) 29 | 30 | ### Available features 31 | * Live postman collection to documentation 32 | * Build postman collection to html/markdown documentation 33 | * Supports multi-level collection build 34 | 35 | ### Usage 36 | * To view live HTML documentation from postman collection use `docgen server -f input-postman-collection.json -p 8000` This will open the html version of postman collection to the defined port 37 | * To view live Markown documentation from postman collection use `docgen server -f input-postman-collection.json -p 8000 -m` This will open the markdown version of postman collection to the defined port 38 | * To make HTML documentation use `docgen build -i input-postman-collection.json -o ~/Downloads/index.html` 39 | * To make Markdown documentation use `docgen build -i input-postman-collection.json -o ~/Downloads/index.md -m` 40 | 41 | ***[Demo markdown API documentation](_examples/example-doc.md)*** 42 | 43 | ### Contributor 44 | 1. [Anondo](https://github.com/Anondo) 45 | 46 | ### Contribution 47 | Your suggestions will be more than appreciated. 48 | [Read the contribution guide here](CONTRIBUTING.md) 49 | 50 | ### See all [contributors](https://github.com/thedevsaddam/docgen/graphs/contributors) 51 | 52 | ### **License** 53 | The **docgen** is an open-source software licensed under the [MIT License](LICENSE.md). 54 | -------------------------------------------------------------------------------- /_examples/example-doc.md: -------------------------------------------------------------------------------- 1 | 2 | # Blog 3 | 4 | This is a simple blogging service provide **REST API** for consumer to build their own blogging platform on demand. 5 | 6 | 7 | 8 | 9 | ## Variables 10 | 11 | | Key | Value | Type | 12 | | --- | ------|-------------| 13 | | blog-server | www.blog-api.com | string | 14 | | username | user | string | 15 | | password | pass | string | 16 | | limit | 20 | string | 17 | 18 | 19 | 20 | ## Endpoints 21 | 22 | * [Users](#users) 23 | 1. [Create user](#1-create-user) 24 | * [Invalid username/password](#i-example-request-invalid-usernamepassword) 25 | * [Empty body](#ii-example-request-empty-body) 26 | * [Validation error](#iii-example-request-validation-error) 27 | * [Success](#iv-example-request-success) 28 | 1. [Fetch user](#2-fetch-user) 29 | * [Success](#i-example-request-success) 30 | * [Invalid user id](#ii-example-request-invalid-user-id) 31 | * [Invalid username/password](#iii-example-request-invalid-usernamepassword) 32 | 1. [Fetch users](#3-fetch-users) 33 | * [Success](#i-example-request-success-1) 34 | * [Invalid user id](#ii-example-request-invalid-user-id-1) 35 | * [Invalid username/password](#iii-example-request-invalid-usernamepassword-1) 36 | 1. [Update user](#4-update-user) 37 | * [Success](#i-example-request-success-2) 38 | * [Validation error](#ii-example-request-validation-error) 39 | * [Empty request body](#iii-example-request-empty-request-body) 40 | * [Invalid username/password](#iv-example-request-invalid-usernamepassword) 41 | * [Invalid user id](#v-example-request-invalid-user-id) 42 | 1. [Delete user](#5-delete-user) 43 | * [Success](#i-example-request-success-3) 44 | * [Invalid user id](#ii-example-request-invalid-user-id-2) 45 | * [Invalid username/password](#iii-example-request-invalid-usernamepassword-2) 46 | * [Articles](#articles) 47 | 1. [Create article](#1-create-article) 48 | * [Validation error](#i-example-request-validation-error) 49 | * [Invalid username/password](#ii-example-request-invalid-usernamepassword) 50 | * [Success](#iii-example-request-success) 51 | 1. [List articles](#2-list-articles) 52 | * [Invalid token](#i-example-request-invalid-token) 53 | * [Success](#ii-example-request-success) 54 | * [Without filtering](#iii-example-request-without-filtering) 55 | * [Users/V2](#usersv2) 56 | 1. [Create user](#1-create-user-1) 57 | * [Validation Error](#i-example-request-validation-error-1) 58 | * [Success](#ii-example-request-success-1) 59 | * [Invalid username/password](#iii-example-request-invalid-usernamepassword-3) 60 | 1. [Update user](#2-update-user) 61 | * [Invalid username/password](#i-example-request-invalid-usernamepassword-1) 62 | * [Success](#ii-example-request-success-2) 63 | * [Ungrouped](#ungrouped) 64 | 1. [Health check](#1-health-check) 65 | * [Error](#i-example-request-error) 66 | * [Success](#ii-example-request-success-3) 67 | 68 | -------- 69 | 70 | 71 | 72 | ## Users 73 | 74 | `Users` directory contains all the user related APIs. For authentication these apis requrie `BasicAuth` 75 | 76 | 77 | 78 | ### 1. Create user 79 | 80 | 81 | Create user use `JSON` payload to create a user 82 | 83 | 84 | ***Endpoint:*** 85 | 86 | ```bash 87 | Method: POST 88 | Type: RAW 89 | URL: {{blog-server}}/v1/users 90 | ``` 91 | 92 | 93 | 94 | ***Body:*** 95 | 96 | ```js 97 | { 98 | "name": "Captain Jack Sparrow", 99 | "bio": "Captain Jack Sparrow is a fictional character and the main protagonist of the Pirates of the Caribbean film series. The character was created by screenwriters Ted Elliott and Terry Rossio and is portrayed by Johnny Depp" 100 | } 101 | ``` 102 | 103 | 104 | 105 | ***More example Requests/Responses:*** 106 | 107 | 108 | ##### I. Example Request: Invalid username/password 109 | 110 | 111 | 112 | ##### I. Example Response: Invalid username/password 113 | ```js 114 | { 115 | "message": "Unauthorized attempt" 116 | } 117 | ``` 118 | 119 | 120 | ***Status Code:*** 401 121 | 122 |
123 | 124 | 125 | 126 | ##### II. Example Request: Empty body 127 | 128 | 129 | 130 | ##### II. Example Response: Empty body 131 | ```js 132 | { 133 | "message": "Invalid request body" 134 | } 135 | ``` 136 | 137 | 138 | ***Status Code:*** 400 139 | 140 |
141 | 142 | 143 | 144 | ##### III. Example Request: Validation error 145 | 146 | 147 | 148 | ***Body:*** 149 | 150 | ```js 151 | { 152 | "name": "", 153 | "bio": "" 154 | } 155 | ``` 156 | 157 | 158 | 159 | ##### III. Example Response: Validation error 160 | ```js 161 | { 162 | "errors": { 163 | "bio": [ 164 | "Bio can not be empty" 165 | ], 166 | "name": [ 167 | "Name can not be empty" 168 | ] 169 | }, 170 | "message": "Validation error" 171 | } 172 | ``` 173 | 174 | 175 | ***Status Code:*** 422 176 | 177 |
178 | 179 | 180 | 181 | ##### IV. Example Request: Success 182 | 183 | 184 | 185 | ***Body:*** 186 | 187 | ```js 188 | { 189 | "name": "Tom Hanks", 190 | "bio": "Thomas Jeffrey Hanks is an American actor and filmmaker. Known for both his comedic and dramatic roles, Hanks is one of the most popular and recognizable film stars worldwide, and is widely regarded as an American cultural icon." 191 | } 192 | ``` 193 | 194 | 195 | 196 | ##### IV. Example Response: Success 197 | ```js 198 | { 199 | "data": { 200 | "id": 1, 201 | "name": "Tom Hanks", 202 | "bio": "Thomas Jeffrey Hanks is an American actor and filmmaker. Known for both his comedic and dramatic roles, Hanks is one of the most popular and recognizable film stars worldwide, and is widely regarded as an American cultural icon." 203 | }, 204 | "message": "User created successfully" 205 | } 206 | ``` 207 | 208 | 209 | ***Status Code:*** 201 210 | 211 |
212 | 213 | 214 | 215 | ### 2. Fetch user 216 | 217 | 218 | Fetch a single user using `id` 219 | 220 | 221 | ***Endpoint:*** 222 | 223 | ```bash 224 | Method: GET 225 | Type: 226 | URL: {{blog-server}}/v1/users/{{id}} 227 | ``` 228 | 229 | 230 | 231 | ***More example Requests/Responses:*** 232 | 233 | 234 | ##### I. Example Request: Success 235 | 236 | 237 | 238 | ##### I. Example Response: Success 239 | ```js 240 | { 241 | "data": { 242 | "id": 1, 243 | "name": "Tom Hanks", 244 | "bio": "Thomas Jeffrey Hanks is an American actor and filmmaker. Known for both his comedic and dramatic roles, Hanks is one of the most popular and recognizable film stars worldwide, and is widely regarded as an American cultural icon." 245 | } 246 | } 247 | ``` 248 | 249 | 250 | ***Status Code:*** 200 251 | 252 |
253 | 254 | 255 | 256 | ##### II. Example Request: Invalid user id 257 | 258 | 259 | 260 | ##### II. Example Response: Invalid user id 261 | ```js 262 | { 263 | "message": "Invalid user id" 264 | } 265 | ``` 266 | 267 | 268 | ***Status Code:*** 400 269 | 270 |
271 | 272 | 273 | 274 | ##### III. Example Request: Invalid username/password 275 | 276 | 277 | 278 | ##### III. Example Response: Invalid username/password 279 | ```js 280 | { 281 | "message": "Unauthorized attempt" 282 | } 283 | ``` 284 | 285 | 286 | ***Status Code:*** 401 287 | 288 |
289 | 290 | 291 | 292 | ### 3. Fetch users 293 | 294 | 295 | Fetch list of users using `pagination` 296 | 297 | 298 | ***Endpoint:*** 299 | 300 | ```bash 301 | Method: GET 302 | Type: 303 | URL: {{blog-server}}/v1/users 304 | ``` 305 | 306 | 307 | 308 | ***Query params:*** 309 | 310 | | Key | Value | Description | 311 | | --- | ------|-------------| 312 | | page | 1 | | 313 | | limit | 20 | | 314 | 315 | 316 | 317 | ***More example Requests/Responses:*** 318 | 319 | 320 | ##### I. Example Request: Success 321 | 322 | 323 | 324 | ***Query:*** 325 | 326 | | Key | Value | Description | 327 | | --- | ------|-------------| 328 | | page | 1 | Page numer is a `integer` which represents the page your are requesting | 329 | | limit | 20 | Limit it the maximum number of users listing in the result. | 330 | 331 | 332 | 333 | ##### I. Example Response: Success 334 | ```js 335 | { 336 | "data": [ 337 | { 338 | "id": 1, 339 | "name": "Tom Hanks", 340 | "bio": "Thomas Jeffrey Hanks is an American actor and filmmaker. Known for both his comedic and dramatic roles, Hanks is one of the most popular and recognizable film stars worldwide, and is widely regarded as an American cultural icon." 341 | }, 342 | { 343 | "id": 2, 344 | "name": "Captain Jack Sparrow", 345 | "bio": "Captain Jack Sparrow is a fictional character and the main protagonist of the Pirates of the Caribbean film series. The character was created by screenwriters Ted Elliott and Terry Rossio and is portrayed by Johnny Depp" 346 | } 347 | ] 348 | } 349 | ``` 350 | 351 | 352 | ***Status Code:*** 200 353 | 354 |
355 | 356 | 357 | 358 | ##### II. Example Request: Invalid user id 359 | 360 | 361 | 362 | ##### II. Example Response: Invalid user id 363 | ```js 364 | { 365 | "message": "Invalid user id" 366 | } 367 | ``` 368 | 369 | 370 | ***Status Code:*** 400 371 | 372 |
373 | 374 | 375 | 376 | ##### III. Example Request: Invalid username/password 377 | 378 | 379 | 380 | ##### III. Example Response: Invalid username/password 381 | ```js 382 | { 383 | "message": "Unauthorized attempt" 384 | } 385 | ``` 386 | 387 | 388 | ***Status Code:*** 401 389 | 390 |
391 | 392 | 393 | 394 | ### 4. Update user 395 | 396 | 397 | Update user use `JSON` payload to create a user 398 | 399 | 400 | ***Endpoint:*** 401 | 402 | ```bash 403 | Method: PUT 404 | Type: RAW 405 | URL: {{blog-server}}/v1/users/{{id}} 406 | ``` 407 | 408 | 409 | 410 | ***Body:*** 411 | 412 | ```js 413 | { 414 | "name": "Captain Jack Sparrow", 415 | "bio": "Captain Jack Sparrow is a fictional character and the main protagonist of the Pirates of the Caribbean film series. The character was created by screenwriters Ted Elliott and Terry Rossio and is portrayed by Johnny Depp" 416 | } 417 | ``` 418 | 419 | 420 | 421 | ***More example Requests/Responses:*** 422 | 423 | 424 | ##### I. Example Request: Success 425 | 426 | 427 | 428 | ***Body:*** 429 | 430 | ```js 431 | { 432 | "name": "Captain Jack Sparrow", 433 | "bio": "Captain Jack Sparrow is a fictional character and the main protagonist of the Pirates of the Caribbean film series. The character was created by screenwriters Ted Elliott and Terry Rossio and is portrayed by Johnny Depp" 434 | } 435 | ``` 436 | 437 | 438 | 439 | ##### I. Example Response: Success 440 | ```js 441 | { 442 | "data": { 443 | "id": 1, 444 | "name": "Captain Jack Sparrow", 445 | "bio": "Captain Jack Sparrow is a fictional character and the main protagonist of the Pirates of the Caribbean film series. The character was created by screenwriters Ted Elliott and Terry Rossio and is portrayed by Johnny Depp" 446 | }, 447 | "message": "User updated successfully" 448 | } 449 | ``` 450 | 451 | 452 | ***Status Code:*** 200 453 | 454 |
455 | 456 | 457 | 458 | ##### II. Example Request: Validation error 459 | 460 | 461 | 462 | ***Body:*** 463 | 464 | ```js 465 | { 466 | "name": "", 467 | "bio": "" 468 | } 469 | ``` 470 | 471 | 472 | 473 | ##### II. Example Response: Validation error 474 | ```js 475 | { 476 | "errors": { 477 | "bio": [ 478 | "Bio can not be empty" 479 | ], 480 | "name": [ 481 | "Name can not be empty" 482 | ] 483 | }, 484 | "message": "Validation error" 485 | } 486 | ``` 487 | 488 | 489 | ***Status Code:*** 422 490 | 491 |
492 | 493 | 494 | 495 | ##### III. Example Request: Empty request body 496 | 497 | 498 | 499 | ##### III. Example Response: Empty request body 500 | ```js 501 | { 502 | "message": "Invalid request body" 503 | } 504 | ``` 505 | 506 | 507 | ***Status Code:*** 400 508 | 509 |
510 | 511 | 512 | 513 | ##### IV. Example Request: Invalid username/password 514 | 515 | 516 | 517 | ##### IV. Example Response: Invalid username/password 518 | ```js 519 | { 520 | "message": "Unauthorized attempt" 521 | } 522 | ``` 523 | 524 | 525 | ***Status Code:*** 401 526 | 527 |
528 | 529 | 530 | 531 | ##### V. Example Request: Invalid user id 532 | 533 | 534 | 535 | ***Body:*** 536 | 537 | ```js 538 | { 539 | "name": "Captain Jack Sparrow", 540 | "bio": "Captain Jack Sparrow is a fictional character and the main protagonist of the Pirates of the Caribbean film series. The character was created by screenwriters Ted Elliott and Terry Rossio and is portrayed by Johnny Depp" 541 | } 542 | ``` 543 | 544 | 545 | 546 | ##### V. Example Response: Invalid user id 547 | ```js 548 | { 549 | "message": "Invalid user id" 550 | } 551 | ``` 552 | 553 | 554 | ***Status Code:*** 400 555 | 556 |
557 | 558 | 559 | 560 | ### 5. Delete user 561 | 562 | 563 | Delete a single user using `id` 564 | 565 | 566 | ***Endpoint:*** 567 | 568 | ```bash 569 | Method: DELETE 570 | Type: 571 | URL: {{blog-server}}/v1/users/{{id}} 572 | ``` 573 | 574 | 575 | 576 | ***More example Requests/Responses:*** 577 | 578 | 579 | ##### I. Example Request: Success 580 | 581 | 582 | 583 | ##### I. Example Response: Success 584 | ```js 585 | { 586 | "message": "User deleted successfully" 587 | } 588 | ``` 589 | 590 | 591 | ***Status Code:*** 200 592 | 593 |
594 | 595 | 596 | 597 | ##### II. Example Request: Invalid user id 598 | 599 | 600 | 601 | ##### II. Example Response: Invalid user id 602 | ```js 603 | { 604 | "message": "Invalid user id" 605 | } 606 | ``` 607 | 608 | 609 | ***Status Code:*** 400 610 | 611 |
612 | 613 | 614 | 615 | ##### III. Example Request: Invalid username/password 616 | 617 | 618 | 619 | ##### III. Example Response: Invalid username/password 620 | ```js 621 | { 622 | "message": "Unauthorized attempt" 623 | } 624 | ``` 625 | 626 | 627 | ***Status Code:*** 401 628 | 629 |
630 | 631 | 632 | 633 | ## Articles 634 | 635 | `Articles` directory contains all the article related APIs. Use `JWT` toekn for authentications for articles API. 636 | 637 | 638 | 639 | ### 1. Create article 640 | 641 | 642 | Create article endpoint is accept form-data to create an article with binary data like file 643 | 644 | 645 | ***Endpoint:*** 646 | 647 | ```bash 648 | Method: POST 649 | Type: FORMDATA 650 | URL: {{blog-server}}/v1/articles 651 | ``` 652 | 653 | 654 | ***Headers:*** 655 | 656 | | Key | Value | Description | 657 | | --- | ------|-------------| 658 | | ExampleHeader1 | ExampleHeader1Value | | 659 | | ExampleHeader2 | ExampleHeader2Value | | 660 | 661 | 662 | 663 | ***Body:*** 664 | 665 | | Key | Value | Description | 666 | | --- | ------|-------------| 667 | | author_id | 1 | Accept `author_id` as the primary *id* of author | 668 | | title | This is title one | The `title` field must be between *1-255* chars | 669 | | body | This is body one | The `body` field must be between *1-2000* chars | 670 | 671 | 672 | 673 | ***More example Requests/Responses:*** 674 | 675 | 676 | ##### I. Example Request: Validation error 677 | 678 | 679 | ***Headers:*** 680 | 681 | | Key | Value | Description | 682 | | --- | ------|-------------| 683 | | ExampleHeader1 | ExampleHeader1Value | | 684 | | ExampleHeader2 | ExampleHeader2Value | | 685 | | Content-Type | application/x-www-form-urlencoded | | 686 | 687 | 688 | 689 | ***Body:*** 690 | 691 | | Key | Value | Description | 692 | | --- | ------|-------------| 693 | | author_id | 1 | Accept `author_id` as the primary *id* of author | 694 | 695 | 696 | 697 | ##### I. Example Response: Validation error 698 | ```js 699 | { 700 | "errors": { 701 | "title": [ 702 | "Title can not be empty" 703 | ], 704 | "body": [ 705 | "Body can not be empty" 706 | ] 707 | }, 708 | "message": "Validation error" 709 | } 710 | ``` 711 | 712 | 713 | ***Status Code:*** 422 714 | 715 |
716 | 717 | 718 | 719 | ##### II. Example Request: Invalid username/password 720 | 721 | 722 | 723 | ##### II. Example Response: Invalid username/password 724 | ```js 725 | { 726 | "message": "Unauthorized attempt" 727 | } 728 | ``` 729 | 730 | 731 | ***Status Code:*** 401 732 | 733 |
734 | 735 | 736 | 737 | ##### III. Example Request: Success 738 | 739 | 740 | ***Headers:*** 741 | 742 | | Key | Value | Description | 743 | | --- | ------|-------------| 744 | | ExampleHeader1 | ExampleHeader1Value | | 745 | | ExampleHeader2 | ExampleHeader2Value | | 746 | 747 | 748 | 749 | ***Body:*** 750 | 751 | | Key | Value | Description | 752 | | --- | ------|-------------| 753 | | author_id | 1 | Accept `author_id` as the primary *id* of author | 754 | | title | This is title one | The `title` field must be between *1-255* chars | 755 | | body | This is body one | The `body` field must be between *1-2000* chars | 756 | 757 | 758 | 759 | ##### III. Example Response: Success 760 | ```js 761 | { 762 | "data": { 763 | "id": 1, 764 | "author_id": 1, 765 | "title": "This is title one", 766 | "body": "This is body one" 767 | }, 768 | "message": "Article created successfully" 769 | } 770 | ``` 771 | 772 | 773 | ***Status Code:*** 201 774 | 775 |
776 | 777 | 778 | 779 | ### 2. List articles 780 | 781 | 782 | List articles endpoint provide article listing with *filtering*, *patination* 783 | 784 | 785 | ***Endpoint:*** 786 | 787 | ```bash 788 | Method: GET 789 | Type: 790 | URL: {{blog-server}}/v1/articles 791 | ``` 792 | 793 | 794 | 795 | ***Query params:*** 796 | 797 | | Key | Value | Description | 798 | | --- | ------|-------------| 799 | | page | {{id}} | Page is a `unsigned integer` which represents the page numer | 800 | | limit | {{limit}} | Limit represents maximum numer of results in the response | 801 | | user_id | {{user_id}} | Filter the articles using *author_id* | 802 | 803 | 804 | 805 | ***More example Requests/Responses:*** 806 | 807 | 808 | ##### I. Example Request: Invalid token 809 | 810 | 811 | ***Headers:*** 812 | 813 | | Key | Value | Description | 814 | | --- | ------|-------------| 815 | | Authorization | Bearer inalid_token | Providing invalid token cause `401` | 816 | 817 | 818 | 819 | ***Query:*** 820 | 821 | | Key | Value | Description | 822 | | --- | ------|-------------| 823 | | page | {{id}} | Page is a `unsigned integer` which represents the page numer | 824 | | limit | {{limit}} | Limit represents maximum numer of results in the response | 825 | | user_id | {{user_id}} | Filter the articles using *author_id* | 826 | 827 | 828 | 829 | ##### I. Example Response: Invalid token 830 | ```js 831 | { 832 | "message": "Unauthorized token" 833 | } 834 | ``` 835 | 836 | 837 | ***Status Code:*** 401 838 | 839 |
840 | 841 | 842 | 843 | ##### II. Example Request: Success 844 | 845 | 846 | ***Headers:*** 847 | 848 | | Key | Value | Description | 849 | | --- | ------|-------------| 850 | | Authorization | Bearer {{jwt_token}} | Providing valid token won't cause *401* | 851 | 852 | 853 | 854 | ***Query:*** 855 | 856 | | Key | Value | Description | 857 | | --- | ------|-------------| 858 | | page | {{id}} | Page is a `unsigned integer` which represents the page numer | 859 | | limit | {{limit}} | Limit represents maximum numer of results in the response | 860 | | user_id | {{user_id}} | Filter the articles using *author_id* | 861 | 862 | 863 | 864 | ##### II. Example Response: Success 865 | ```js 866 | { 867 | "data": [ 868 | { 869 | "id": 3, 870 | "user_id": 10, 871 | "title": "Article 13", 872 | "body": "This is article three" 873 | }, 874 | { 875 | "id": 2, 876 | "user_id": 10, 877 | "title": "Article 2", 878 | "body": "This is article two" 879 | }, 880 | { 881 | "id": 1, 882 | "user_id": 10, 883 | "title": "Article 1", 884 | "body": "This is article one" 885 | } 886 | ] 887 | } 888 | ``` 889 | 890 | 891 | ***Status Code:*** 200 892 | 893 |
894 | 895 | 896 | 897 | ##### III. Example Request: Without filtering 898 | 899 | 900 | ***Headers:*** 901 | 902 | | Key | Value | Description | 903 | | --- | ------|-------------| 904 | | Authorization | Bearer {{jwt_token}} | Providing valid token won't cause *401* | 905 | 906 | 907 | 908 | ***Query:*** 909 | 910 | | Key | Value | Description | 911 | | --- | ------|-------------| 912 | | page | {{id}} | Page is a `unsigned integer` which represents the page numer | 913 | | limit | {{limit}} | Limit represents maximum numer of results in the response | 914 | 915 | 916 | 917 | ##### III. Example Response: Without filtering 918 | ```js 919 | { 920 | "data": [ 921 | { 922 | "id": 3, 923 | "user_id": 10, 924 | "title": "Article 13", 925 | "body": "This is article three" 926 | }, 927 | { 928 | "id": 2, 929 | "user_id": 10, 930 | "title": "Article 2", 931 | "body": "This is article two" 932 | }, 933 | { 934 | "id": 1, 935 | "user_id": 10, 936 | "title": "Article 1", 937 | "body": "This is article one" 938 | } 939 | ] 940 | } 941 | ``` 942 | 943 | 944 | ***Status Code:*** 200 945 | 946 |
947 | 948 | 949 | 950 | ## Users/V2 951 | 952 | 953 | 954 | ### 1. Create user 955 | 956 | 957 | Create user use `JSON` payload to create a user 958 | 959 | 960 | ***Endpoint:*** 961 | 962 | ```bash 963 | Method: POST 964 | Type: URLENCODED 965 | URL: {{blog-server}}/v2/users 966 | ``` 967 | 968 | 969 | 970 | ***Body:*** 971 | 972 | 973 | | Key | Value | Description | 974 | | --- | ------|-------------| 975 | | name | Tom Hanks | | 976 | | bio | Thomas Jeffrey Hanks is an American actor and filmmaker. Known for both his comedic and dramatic roles, Hanks is one of the most popular and recognizable film stars worldwide, and is widely regarded as an American cultural icon. | | 977 | 978 | 979 | 980 | ***More example Requests/Responses:*** 981 | 982 | 983 | ##### I. Example Request: Validation Error 984 | 985 | 986 | 987 | ***Body:*** 988 | 989 | 990 | | Key | Value | Description | 991 | | --- | ------|-------------| 992 | | name | | | 993 | | bio | | | 994 | 995 | 996 | 997 | ##### I. Example Response: Validation Error 998 | ```js 999 | { 1000 | "errors": { 1001 | "bio": [ 1002 | "Bio can not be empty" 1003 | ], 1004 | "name": [ 1005 | "Name can not be empty" 1006 | ] 1007 | }, 1008 | "message": "Validation error" 1009 | } 1010 | ``` 1011 | 1012 | 1013 | ***Status Code:*** 422 1014 | 1015 |
1016 | 1017 | 1018 | 1019 | ##### II. Example Request: Success 1020 | 1021 | 1022 | 1023 | ***Body:*** 1024 | 1025 | 1026 | | Key | Value | Description | 1027 | | --- | ------|-------------| 1028 | | name | Tom Hanks | | 1029 | | bio | Thomas Jeffrey Hanks is an American actor and filmmaker. Known for both his comedic and dramatic roles, Hanks is one of the most popular and recognizable film stars worldwide, and is widely regarded as an American cultural icon. | | 1030 | 1031 | 1032 | 1033 | ##### II. Example Response: Success 1034 | ```js 1035 | { 1036 | "data": { 1037 | "id": 1, 1038 | "name": "Tom Hanks", 1039 | "bio": "Thomas Jeffrey Hanks is an American actor and filmmaker. Known for both his comedic and dramatic roles, Hanks is one of the most popular and recognizable film stars worldwide, and is widely regarded as an American cultural icon." 1040 | }, 1041 | "message": "User created successfully" 1042 | } 1043 | ``` 1044 | 1045 | 1046 | ***Status Code:*** 201 1047 | 1048 |
1049 | 1050 | 1051 | 1052 | ##### III. Example Request: Invalid username/password 1053 | 1054 | 1055 | 1056 | ##### III. Example Response: Invalid username/password 1057 | ```js 1058 | { 1059 | "message": "Unauthorized attempt" 1060 | } 1061 | ``` 1062 | 1063 | 1064 | ***Status Code:*** 401 1065 | 1066 |
1067 | 1068 | 1069 | 1070 | ### 2. Update user 1071 | 1072 | 1073 | Use **urlencoded** data to update user partially 1074 | 1075 | 1076 | ***Endpoint:*** 1077 | 1078 | ```bash 1079 | Method: PATCH 1080 | Type: URLENCODED 1081 | URL: {{blog-server}}/v2/users/1 1082 | ``` 1083 | 1084 | 1085 | 1086 | ***Body:*** 1087 | 1088 | 1089 | | Key | Value | Description | 1090 | | --- | ------|-------------| 1091 | | name | Mr. Tom Hanks | | 1092 | 1093 | 1094 | 1095 | ***More example Requests/Responses:*** 1096 | 1097 | 1098 | ##### I. Example Request: Invalid username/password 1099 | 1100 | 1101 | 1102 | ##### I. Example Response: Invalid username/password 1103 | ```js 1104 | { 1105 | "message": "Unauthorized attempt" 1106 | } 1107 | ``` 1108 | 1109 | 1110 | ***Status Code:*** 401 1111 | 1112 |
1113 | 1114 | 1115 | 1116 | ##### II. Example Request: Success 1117 | 1118 | 1119 | 1120 | ***Body:*** 1121 | 1122 | 1123 | | Key | Value | Description | 1124 | | --- | ------|-------------| 1125 | | name | Mr. Tom Hanks | | 1126 | 1127 | 1128 | 1129 | ##### II. Example Response: Success 1130 | ```js 1131 | { 1132 | "data": { 1133 | "id": 1, 1134 | "name": "Mr. Tom Hanks", 1135 | "bio": "Thomas Jeffrey Hanks is an American actor and filmmaker. Known for both his comedic and dramatic roles, Hanks is one of the most popular and recognizable film stars worldwide, and is widely regarded as an American cultural icon." 1136 | }, 1137 | "message": "User partial update successful" 1138 | } 1139 | ``` 1140 | 1141 | 1142 | ***Status Code:*** 206 1143 | 1144 |
1145 | 1146 | 1147 | 1148 | ## Ungrouped 1149 | 1150 | 1151 | 1152 | ### 1. Health check 1153 | 1154 | 1155 | System health check endpoint provide system health status for `probe` 1156 | 1157 | 1158 | ***Endpoint:*** 1159 | 1160 | ```bash 1161 | Method: GET 1162 | Type: 1163 | URL: {{blog-server}}/ 1164 | ``` 1165 | 1166 | 1167 | 1168 | ***More example Requests/Responses:*** 1169 | 1170 | 1171 | ##### I. Example Request: Error 1172 | 1173 | 1174 | 1175 | ##### I. Example Response: Error 1176 | ```js 1177 | { 1178 | "system": "Failed" 1179 | } 1180 | ``` 1181 | 1182 | 1183 | ***Status Code:*** 500 1184 | 1185 |
1186 | 1187 | 1188 | 1189 | ##### II. Example Request: Success 1190 | 1191 | 1192 | 1193 | ##### II. Example Response: Success 1194 | ```js 1195 | { 1196 | "system": "OK" 1197 | } 1198 | ``` 1199 | 1200 | 1201 | ***Status Code:*** 200 1202 | 1203 |
1204 | 1205 | 1206 | 1207 | --- 1208 | [Back to top](#blog) 1209 | 1210 | >Generated at 2022-01-25 15:47:32 by [docgen](https://github.com/thedevsaddam/docgen) 1211 | -------------------------------------------------------------------------------- /_examples/example_env.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "4e932f2e-cc70-4aa3-9b84-60bc1f145ff5", 3 | "name": "blog", 4 | "values": [ 5 | { 6 | "key": "blog-server", 7 | "value": "www.localhost:9000", 8 | "enabled": true 9 | }, 10 | { 11 | "key": "id", 12 | "value": "1", 13 | "enabled": true 14 | }, 15 | { 16 | "key": "limit", 17 | "value": "20", 18 | "enabled": true 19 | }, 20 | { 21 | "key": "user_id", 22 | "value": "1", 23 | "enabled": true 24 | } 25 | ], 26 | "_postman_variable_scope": "environment", 27 | "_postman_exported_at": "2020-05-11T08:57:20.194Z", 28 | "_postman_exported_using": "Postman/7.24.0" 29 | } -------------------------------------------------------------------------------- /assets/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.7 (http://getbootstrap.com) 3 | * Copyright 2011-2016 Twitter, Inc. 4 | * Licensed under the MIT license 5 | */ 6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>3)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){if(a(b.target).is(this))return b.handleObj.handler.apply(this,arguments)}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.7",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a("#"===f?[]:f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.7",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c).prop(c,!0)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c).prop(c,!1))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target).closest(".btn");b.call(d,"toggle"),a(c.target).is('input[type="radio"], input[type="checkbox"]')||(c.preventDefault(),d.is("input,button")?d.trigger("focus"):d.find("input:visible,button:visible").first().trigger("focus"))}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.7",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));if(!(a>this.$items.length-1||a<0))return this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){if(!this.sliding)return this.slide("next")},c.prototype.prev=function(){if(!this.sliding)return this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.7",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.7",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);if(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),!c.isInStateTrue())return clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null,a.$element=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;!e&&/destroy|hide/.test(b)||(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.7",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.7",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.7",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return e=a-d&&"bottom"},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); -------------------------------------------------------------------------------- /assets/github-markdown.min.css: -------------------------------------------------------------------------------- 1 | @font-face{font-family:octicons-link;src:url(data:font/woff;charset=utf-8;base64,d09GRgABAAAAAAZwABAAAAAACFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEU0lHAAAGaAAAAAgAAAAIAAAAAUdTVUIAAAZcAAAACgAAAAoAAQAAT1MvMgAAAyQAAABJAAAAYFYEU3RjbWFwAAADcAAAAEUAAACAAJThvmN2dCAAAATkAAAABAAAAAQAAAAAZnBnbQAAA7gAAACyAAABCUM+8IhnYXNwAAAGTAAAABAAAAAQABoAI2dseWYAAAFsAAABPAAAAZwcEq9taGVhZAAAAsgAAAA0AAAANgh4a91oaGVhAAADCAAAABoAAAAkCA8DRGhtdHgAAAL8AAAADAAAAAwGAACfbG9jYQAAAsAAAAAIAAAACABiATBtYXhwAAACqAAAABgAAAAgAA8ASm5hbWUAAAToAAABQgAAAlXu73sOcG9zdAAABiwAAAAeAAAAME3QpOBwcmVwAAAEbAAAAHYAAAB/aFGpk3jaTY6xa8JAGMW/O62BDi0tJLYQincXEypYIiGJjSgHniQ6umTsUEyLm5BV6NDBP8Tpts6F0v+k/0an2i+itHDw3v2+9+DBKTzsJNnWJNTgHEy4BgG3EMI9DCEDOGEXzDADU5hBKMIgNPZqoD3SilVaXZCER3/I7AtxEJLtzzuZfI+VVkprxTlXShWKb3TBecG11rwoNlmmn1P2WYcJczl32etSpKnziC7lQyWe1smVPy/Lt7Kc+0vWY/gAgIIEqAN9we0pwKXreiMasxvabDQMM4riO+qxM2ogwDGOZTXxwxDiycQIcoYFBLj5K3EIaSctAq2kTYiw+ymhce7vwM9jSqO8JyVd5RH9gyTt2+J/yUmYlIR0s04n6+7Vm1ozezUeLEaUjhaDSuXHwVRgvLJn1tQ7xiuVv/ocTRF42mNgZGBgYGbwZOBiAAFGJBIMAAizAFoAAABiAGIAznjaY2BkYGAA4in8zwXi+W2+MjCzMIDApSwvXzC97Z4Ig8N/BxYGZgcgl52BCSQKAA3jCV8CAABfAAAAAAQAAEB42mNgZGBg4f3vACQZQABIMjKgAmYAKEgBXgAAeNpjYGY6wTiBgZWBg2kmUxoDA4MPhGZMYzBi1AHygVLYQUCaawqDA4PChxhmh/8ODDEsvAwHgMKMIDnGL0x7gJQCAwMAJd4MFwAAAHjaY2BgYGaA4DAGRgYQkAHyGMF8NgYrIM3JIAGVYYDT+AEjAwuDFpBmA9KMDEwMCh9i/v8H8sH0/4dQc1iAmAkALaUKLgAAAHjaTY9LDsIgEIbtgqHUPpDi3gPoBVyRTmTddOmqTXThEXqrob2gQ1FjwpDvfwCBdmdXC5AVKFu3e5MfNFJ29KTQT48Ob9/lqYwOGZxeUelN2U2R6+cArgtCJpauW7UQBqnFkUsjAY/kOU1cP+DAgvxwn1chZDwUbd6CFimGXwzwF6tPbFIcjEl+vvmM/byA48e6tWrKArm4ZJlCbdsrxksL1AwWn/yBSJKpYbq8AXaaTb8AAHja28jAwOC00ZrBeQNDQOWO//sdBBgYGRiYWYAEELEwMTE4uzo5Zzo5b2BxdnFOcALxNjA6b2ByTswC8jYwg0VlNuoCTWAMqNzMzsoK1rEhNqByEyerg5PMJlYuVueETKcd/89uBpnpvIEVomeHLoMsAAe1Id4AAAAAAAB42oWQT07CQBTGv0JBhagk7HQzKxca2sJCE1hDt4QF+9JOS0nbaaYDCQfwCJ7Au3AHj+LO13FMmm6cl7785vven0kBjHCBhfpYuNa5Ph1c0e2Xu3jEvWG7UdPDLZ4N92nOm+EBXuAbHmIMSRMs+4aUEd4Nd3CHD8NdvOLTsA2GL8M9PODbcL+hD7C1xoaHeLJSEao0FEW14ckxC+TU8TxvsY6X0eLPmRhry2WVioLpkrbp84LLQPGI7c6sOiUzpWIWS5GzlSgUzzLBSikOPFTOXqly7rqx0Z1Q5BAIoZBSFihQYQOOBEdkCOgXTOHA07HAGjGWiIjaPZNW13/+lm6S9FT7rLHFJ6fQbkATOG1j2OFMucKJJsxIVfQORl+9Jyda6Sl1dUYhSCm1dyClfoeDve4qMYdLEbfqHf3O/AdDumsjAAB42mNgYoAAZQYjBmyAGYQZmdhL8zLdDEydARfoAqIAAAABAAMABwAKABMAB///AA8AAQAAAAAAAAAAAAAAAAABAAAAAA==) format('woff')}.markdown-body .octicon{display:inline-block;fill:currentColor;vertical-align:text-bottom}.markdown-body .anchor{float:left;line-height:1;margin-left:-20px;padding-right:4px}.markdown-body .anchor:focus{outline:0}.markdown-body h1 .octicon-link,.markdown-body h2 .octicon-link,.markdown-body h3 .octicon-link,.markdown-body h4 .octicon-link,.markdown-body h5 .octicon-link,.markdown-body h6 .octicon-link{color:#1b1f23;vertical-align:middle;visibility:hidden}.markdown-body h1:hover .anchor,.markdown-body h2:hover .anchor,.markdown-body h3:hover .anchor,.markdown-body h4:hover .anchor,.markdown-body h5:hover .anchor,.markdown-body h6:hover .anchor{text-decoration:none}.markdown-body h1:hover .anchor .octicon-link,.markdown-body h2:hover .anchor .octicon-link,.markdown-body h3:hover .anchor .octicon-link,.markdown-body h4:hover .anchor .octicon-link,.markdown-body h5:hover .anchor .octicon-link,.markdown-body h6:hover .anchor .octicon-link{visibility:visible}.markdown-body{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;color:#24292e;line-height:1.5;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;font-size:16px;line-height:1.5;word-wrap:break-word}.markdown-body .pl-c{color:#6a737d}.markdown-body .pl-c1,.markdown-body .pl-s .pl-v{color:#005cc5}.markdown-body .pl-e,.markdown-body .pl-en{color:#6f42c1}.markdown-body .pl-s .pl-s1,.markdown-body .pl-smi{color:#24292e}.markdown-body .pl-ent{color:#22863a}.markdown-body .pl-k{color:#d73a49}.markdown-body .pl-pds,.markdown-body .pl-s,.markdown-body .pl-s .pl-pse .pl-s1,.markdown-body .pl-sr,.markdown-body .pl-sr .pl-cce,.markdown-body .pl-sr .pl-sra,.markdown-body .pl-sr .pl-sre{color:#032f62}.markdown-body .pl-smw,.markdown-body .pl-v{color:#e36209}.markdown-body .pl-bu{color:#b31d28}.markdown-body .pl-ii{background-color:#b31d28;color:#fafbfc}.markdown-body .pl-c2{background-color:#d73a49;color:#fafbfc}.markdown-body .pl-c2:before{content:"^M"}.markdown-body .pl-sr .pl-cce{color:#22863a;font-weight:700}.markdown-body .pl-ml{color:#735c0f}.markdown-body .pl-mh,.markdown-body .pl-mh .pl-en,.markdown-body .pl-ms{color:#005cc5;font-weight:700}.markdown-body .pl-mi{color:#24292e;font-style:italic}.markdown-body .pl-mb{color:#24292e;font-weight:700}.markdown-body .pl-md{background-color:#ffeef0;color:#b31d28}.markdown-body .pl-mi1{background-color:#f0fff4;color:#22863a}.markdown-body .pl-mc{background-color:#ffebda;color:#e36209}.markdown-body .pl-mi2{background-color:#005cc5;color:#f6f8fa}.markdown-body .pl-mdr{color:#6f42c1;font-weight:700}.markdown-body .pl-ba{color:#586069}.markdown-body .pl-sg{color:#959da5}.markdown-body .pl-corl{color:#032f62;text-decoration:underline}.markdown-body details{display:block}.markdown-body summary{display:list-item}.markdown-body a{background-color:transparent}.markdown-body a:active,.markdown-body a:hover{outline-width:0}.markdown-body strong{font-weight:inherit;font-weight:bolder}.markdown-body h1{font-size:2em;margin:.67em 0}.markdown-body img{border-style:none}.markdown-body code,.markdown-body kbd,.markdown-body pre{font-family:monospace,monospace;font-size:1em}.markdown-body hr{box-sizing:content-box;height:0;overflow:visible}.markdown-body input{font:inherit;margin:0}.markdown-body input{overflow:visible}.markdown-body [type=checkbox]{box-sizing:border-box;padding:0}.markdown-body *{box-sizing:border-box}.markdown-body input{font-family:inherit;font-size:inherit;line-height:inherit}.markdown-body a{color:#0366d6;text-decoration:none}.markdown-body a:hover{text-decoration:underline}.markdown-body strong{font-weight:600}.markdown-body hr{background:0 0;border:0;border-bottom:1px solid #dfe2e5;height:0;margin:15px 0;overflow:hidden}.markdown-body hr:before{content:"";display:table}.markdown-body hr:after{clear:both;content:"";display:table}.markdown-body table{border-collapse:collapse;border-spacing:0}.markdown-body td,.markdown-body th{padding:0}.markdown-body details summary{cursor:pointer}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{margin-bottom:0;margin-top:0}.markdown-body h1{font-size:32px}.markdown-body h1,.markdown-body h2{font-weight:600}.markdown-body h2{font-size:24px}.markdown-body h3{font-size:20px}.markdown-body h3,.markdown-body h4{font-weight:600}.markdown-body h4{font-size:16px}.markdown-body h5{font-size:14px}.markdown-body h5,.markdown-body h6{font-weight:600}.markdown-body h6{font-size:12px}.markdown-body p{margin-bottom:10px;margin-top:0}.markdown-body blockquote{margin:0}.markdown-body ol,.markdown-body ul{margin-bottom:0;margin-top:0;padding-left:0}.markdown-body ol ol,.markdown-body ul ol{list-style-type:lower-roman}.markdown-body ol ol ol,.markdown-body ol ul ol,.markdown-body ul ol ol,.markdown-body ul ul ol{list-style-type:lower-alpha}.markdown-body dd{margin-left:0}.markdown-body code,.markdown-body pre{font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace;font-size:12px}.markdown-body pre{margin-bottom:0;margin-top:0}.markdown-body input::-webkit-inner-spin-button,.markdown-body input::-webkit-outer-spin-button{-webkit-appearance:none;appearance:none;margin:0}.markdown-body .border{border:1px solid #e1e4e8!important}.markdown-body .border-0{border:0!important}.markdown-body .border-bottom{border-bottom:1px solid #e1e4e8!important}.markdown-body .rounded-1{border-radius:3px!important}.markdown-body .bg-white{background-color:#fff!important}.markdown-body .bg-gray-light{background-color:#fafbfc!important}.markdown-body .text-gray-light{color:#6a737d!important}.markdown-body .mb-0{margin-bottom:0!important}.markdown-body .my-2{margin-bottom:8px!important;margin-top:8px!important}.markdown-body .pl-0{padding-left:0!important}.markdown-body .py-0{padding-bottom:0!important;padding-top:0!important}.markdown-body .pl-1{padding-left:4px!important}.markdown-body .pl-2{padding-left:8px!important}.markdown-body .py-2{padding-bottom:8px!important;padding-top:8px!important}.markdown-body .pl-3,.markdown-body .px-3{padding-left:16px!important}.markdown-body .px-3{padding-right:16px!important}.markdown-body .pl-4{padding-left:24px!important}.markdown-body .pl-5{padding-left:32px!important}.markdown-body .pl-6{padding-left:40px!important}.markdown-body .f6{font-size:12px!important}.markdown-body .lh-condensed{line-height:1.25!important}.markdown-body .text-bold{font-weight:600!important}.markdown-body:before{content:"";display:table}.markdown-body:after{clear:both;content:"";display:table}.markdown-body>:first-child{margin-top:0!important}.markdown-body>:last-child{margin-bottom:0!important}.markdown-body a:not([href]){color:inherit;text-decoration:none}.markdown-body blockquote,.markdown-body dl,.markdown-body ol,.markdown-body p,.markdown-body pre,.markdown-body table,.markdown-body ul{margin-bottom:16px;margin-top:0}.markdown-body hr{background-color:#e1e4e8;border:0;height:.25em;margin:24px 0;padding:0}.markdown-body blockquote{border-left:.25em solid #dfe2e5;color:#6a737d;padding:0 1em}.markdown-body blockquote>:first-child{margin-top:0}.markdown-body blockquote>:last-child{margin-bottom:0}.markdown-body kbd{background-color:#fafbfc;border:1px solid #c6cbd1;border-bottom-color:#959da5;border-radius:3px;box-shadow:inset 0 -1px 0 #959da5;color:#444d56;display:inline-block;font-size:11px;line-height:10px;padding:3px 5px;vertical-align:middle}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{font-weight:600;line-height:1.25;margin-bottom:16px;margin-top:24px}.markdown-body h1{font-size:2em}.markdown-body h1,.markdown-body h2{border-bottom:1px solid #eaecef;padding-bottom:.3em}.markdown-body h2{font-size:1.5em}.markdown-body h3{font-size:1.25em}.markdown-body h4{font-size:1em}.markdown-body h5{font-size:.875em}.markdown-body h6{color:#6a737d;font-size:.85em}.markdown-body ol,.markdown-body ul{padding-left:2em}.markdown-body ol ol,.markdown-body ol ul,.markdown-body ul ol,.markdown-body ul ul{margin-bottom:0;margin-top:0}.markdown-body li{word-wrap:break-all}.markdown-body li>p{margin-top:16px}.markdown-body li+li{margin-top:.25em}.markdown-body dl{padding:0}.markdown-body dl dt{font-size:1em;font-style:italic;font-weight:600;margin-top:16px;padding:0}.markdown-body dl dd{margin-bottom:16px;padding:0 16px}.markdown-body table{display:block;overflow:auto;width:100%}.markdown-body table th{font-weight:600}.markdown-body table td,.markdown-body table th{border:1px solid #dfe2e5;padding:6px 13px}.markdown-body table tr{background-color:#fff;border-top:1px solid #c6cbd1}.markdown-body table tr:nth-child(2n){background-color:#f6f8fa}.markdown-body img{background-color:#fff;box-sizing:content-box;max-width:100%}.markdown-body img[align=right]{padding-left:20px}.markdown-body img[align=left]{padding-right:20px}.markdown-body code{background-color:rgba(27,31,35,.05);border-radius:3px;font-size:85%;margin:0;padding:.2em .4em}.markdown-body pre{word-wrap:normal}.markdown-body pre>code{background:0 0;border:0;font-size:100%;margin:0;padding:0;white-space:pre;word-break:normal}.markdown-body .highlight{margin-bottom:16px}.markdown-body .highlight pre{margin-bottom:0;word-break:normal}.markdown-body .highlight pre,.markdown-body pre{background-color:#f6f8fa;border-radius:3px;font-size:85%;line-height:1.45;overflow:auto;padding:16px}.markdown-body pre code{background-color:transparent;border:0;display:inline;line-height:inherit;margin:0;max-width:auto;overflow:visible;padding:0;word-wrap:normal}.markdown-body .commit-tease-sha{color:#444d56;display:inline-block;font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace;font-size:90%}.markdown-body .blob-wrapper{border-bottom-left-radius:3px;border-bottom-right-radius:3px;overflow-x:auto;overflow-y:hidden}.markdown-body .blob-wrapper-embedded{max-height:240px;overflow-y:auto}.markdown-body .blob-num{-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none;color:rgba(27,31,35,.3);cursor:pointer;font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace;font-size:12px;line-height:20px;min-width:50px;padding-left:10px;padding-right:10px;text-align:right;user-select:none;vertical-align:top;white-space:nowrap;width:1%}.markdown-body .blob-num:hover{color:rgba(27,31,35,.6)}.markdown-body .blob-num:before{content:attr(data-line-number)}.markdown-body .blob-code{line-height:20px;padding-left:10px;padding-right:10px;position:relative;vertical-align:top}.markdown-body .blob-code-inner{color:#24292e;font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace;font-size:12px;overflow:visible;white-space:pre;word-wrap:normal}.markdown-body .pl-token.active,.markdown-body .pl-token:hover{background:#ffea7f;cursor:pointer}.markdown-body kbd{background-color:#fafbfc;border:1px solid #d1d5da;border-bottom-color:#c6cbd1;border-radius:3px;box-shadow:inset 0 -1px 0 #c6cbd1;color:#444d56;display:inline-block;font:11px SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace;line-height:10px;padding:3px 5px;vertical-align:middle}.markdown-body :checked+.radio-label{border-color:#0366d6;position:relative;z-index:1}.markdown-body .tab-size[data-tab-size="1"]{-moz-tab-size:1;tab-size:1}.markdown-body .tab-size[data-tab-size="2"]{-moz-tab-size:2;tab-size:2}.markdown-body .tab-size[data-tab-size="3"]{-moz-tab-size:3;tab-size:3}.markdown-body .tab-size[data-tab-size="4"]{-moz-tab-size:4;tab-size:4}.markdown-body .tab-size[data-tab-size="5"]{-moz-tab-size:5;tab-size:5}.markdown-body .tab-size[data-tab-size="6"]{-moz-tab-size:6;tab-size:6}.markdown-body .tab-size[data-tab-size="7"]{-moz-tab-size:7;tab-size:7}.markdown-body .tab-size[data-tab-size="8"]{-moz-tab-size:8;tab-size:8}.markdown-body .tab-size[data-tab-size="9"]{-moz-tab-size:9;tab-size:9}.markdown-body .tab-size[data-tab-size="10"]{-moz-tab-size:10;tab-size:10}.markdown-body .tab-size[data-tab-size="11"]{-moz-tab-size:11;tab-size:11}.markdown-body .tab-size[data-tab-size="12"]{-moz-tab-size:12;tab-size:12}.markdown-body .task-list-item{list-style-type:none}.markdown-body .task-list-item+.task-list-item{margin-top:3px}.markdown-body .task-list-item input{margin:0 .2em .25em -1.6em;vertical-align:middle}.markdown-body hr{border-bottom-color:#eee}.markdown-body .pl-0{padding-left:0!important}.markdown-body .pl-1{padding-left:4px!important}.markdown-body .pl-2{padding-left:8px!important}.markdown-body .pl-3{padding-left:16px!important}.markdown-body .pl-4{padding-left:24px!important}.markdown-body .pl-5{padding-left:32px!important}.markdown-body .pl-6{padding-left:40px!important}.markdown-body .pl-7{padding-left:48px!important}.markdown-body .pl-8{padding-left:64px!important}.markdown-body .pl-9{padding-left:80px!important}.markdown-body .pl-10{padding-left:96px!important}.markdown-body .pl-11{padding-left:112px!important}.markdown-body .pl-12{padding-left:128px!important} 2 | /*# sourceMappingURL=github-markdown.min.css.map */ -------------------------------------------------------------------------------- /assets/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | @{{ if .Data.Info.Name }}@ 10 | @{{ .Data.Info.Name | e }}@ 11 | @{{ end }}@ 12 | @{{ if .Data.Info.Description }}@ 13 |  |  14 | @{{ .Data.Info.Description }}@ 15 | @{{ end }}@ 16 | 17 | 18 | 25 | 26 | 27 | 31 | 32 | 33 |
34 |
35 | 36 |
37 |

@{{ .Data.Info.Name }}@

38 |

39 | @{{ .Data.Info.Description | markdown | e | html }}@ 40 |

41 |
42 | 43 |
44 | 45 |
46 |
    47 | @{{ range $index, $c := .Data.Collections }}@ 48 |
  • 49 | @{{ $c.Name }}@ 50 | 55 |
  • 56 | @{{ end }}@ 57 |
58 |
59 | 60 | 61 |
62 | 63 | @{{ range $di, $d := .Data.Collections }}@ 64 |
65 |
66 |

67 | @{{ $d.Name }}@ 68 | @{{ len $d.Items }}@ 69 |

70 |
71 |
72 |

73 | @{{ $d.Description | markdown | e | html }}@ 74 |

75 |
76 |
77 | 78 | @{{ range $ii, $item := $d.Items }}@ 79 |
80 | 98 |
99 |
100 | @{{ if $item.Request.Description }}@ 101 |
Description
102 |
103 | 104 | @{{ $item.Request.Description | markdown | e | html }}@ 105 | 106 |

107 | @{{ end }}@ 108 | @{{ if $item.Request.Headers }}@ 109 |
Headers
110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | @{{ range $ih, $h := $item.Request.Headers }}@ 120 | 121 | 122 | 123 | 124 | 125 | @{{ end }}@ 126 | 127 |
KeyValueDescription
@{{ $h.Key | e }}@@{{ $h.Value |e }}@@{{ $h.Description | markdown | e | html }}@
128 | @{{ end }}@ 129 | @{{ if $item.Request.URL.Query }}@ 130 |
Query
131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | @{{ range $iq, $q := $item.Request.URL.Query }}@ 141 | 142 | 143 | 144 | 145 | 146 | @{{ end }}@ 147 | 148 |
KeyValueDescription
@{{ $q.Key | e }}@@{{ $q.Value | e }}@@{{ $q.Description | markdown | e | html }}@
149 | @{{ end }}@ 150 | @{{ if $item.Request.URL.Variables }}@ 151 |
URL Variables
152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | @{{ range $iq, $q := $item.Request.URL.Variables }}@ 162 | 163 | 164 | 165 | 166 | 167 | @{{ end }}@ 168 | 169 |
KeyValueDescription
@{{ $q.Key | e }}@@{{ $q.Value | e }}@@{{ $q.Description | markdown | e | html }}@
170 | @{{ end }}@ 171 | 172 | @{{ if $item.Request.Body.Mode}}@ 173 | @{{ if eq $item.Request.Body.Mode "raw"}}@ 174 | @{{ if $item.Request.Body.Raw }}@ 175 |
Body
176 | 177 | @{{ $item.Request.Body.Raw | eHTML }}@ 178 | 179 |
180 | @{{ end }}@ 181 | @{{ end }}@ 182 | @{{ end }}@ 183 | 184 | @{{ if $item.Request.Body.Mode}}@ 185 | @{{ if eq $item.Request.Body.Mode "formdata"}}@ 186 |
Body
187 | @{{ if $item.Request.Body.FormData }}@ 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | @{{ range $if, $f := $item.Request.Body.FormData }}@ 198 | 199 | 200 | 201 | 202 | 203 | @{{ end }}@ 204 | 205 |
KeyValueDescription
@{{ $f.Key | e }}@@{{ $f.Value | e }}@@{{ $f.Description | markdown | e | html }}@
206 | @{{ end }}@ 207 | @{{ end }}@ 208 | @{{ end }}@ 209 | 210 | @{{ if $item.Request.Body.Mode}}@ 211 | @{{ if eq $item.Request.Body.Mode "urlencoded"}}@ 212 |
Body
213 | @{{ if $item.Request.Body.URLEncoded }}@ 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | @{{ range $iu, $u := $item.Request.Body.URLEncoded }}@ 224 | 225 | 226 | 227 | 228 | 229 | @{{ end }}@ 230 | 231 |
KeyValueDescription
@{{ $u.Key | e }}@@{{ $u.Value | e }}@@{{ $u.Description | markdown | e | html }}@
232 | @{{ end }}@ 233 | @{{ end }}@ 234 | @{{ end }}@ 235 | 236 | @{{ if $item.Responses }}@ 237 | 243 |
244 | 245 | @{{ range $ir, $resp := $item.Responses }}@ 246 | 387 | 388 | @{{ end }}@ 389 | 390 | 391 | @{{ end }}@ 392 |
393 |
394 |
395 | @{{ end }}@ 396 | 397 |
398 |
399 |
400 |
401 | @{{ end }}@ 402 | 403 | 404 | @{{ if .Data.Variables }}@ 405 |
406 |
407 |

408 | Available Variables 409 | @{{ len .Data.Variables }}@ 410 |

411 |
412 |
413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | @{{ range $iv, $v := .Data.Variables }}@ 423 | 424 | 425 | 426 | 427 | 428 | @{{ end }}@ 429 | 430 |
KeyValueType
@{{ $v.Key }}@@{{ $v.Value }}@@{{ $v.Type }}@
431 |
432 |
433 |
434 | @{{ end }}@ 435 | 436 |
437 | 438 |
439 |
440 | 441 | 446 | 447 |
448 |
449 |
450 |
451 |
452 | Generated at @{{date_time}}@ by docgen 453 |
454 |
455 |
456 | 457 | -------------------------------------------------------------------------------- /assets/index.md: -------------------------------------------------------------------------------- 1 | 2 | @{{if .Data.Info.Name -}}@ 3 | # @{{ .Data.Info.Name | trim }}@ 4 | @{{ end }}@ 5 | @{{ if .Data.Info.Description }}@ 6 | @{{- .Data.Info.Description -}}@ 7 | @{{ end }}@ 8 | 9 | 10 | @{{- $numCollections := len .Data.Collections}}@ 11 | 12 | @{{- if eq $numCollections 1 }}@ 13 | @{{- range $index, $c := .Data.Collections -}}@ 14 | @{{- range $i, $item := $c.Items }}@ 15 | 1. [@{{ $item.Name | trim }}@](#@{{ merge $i $item.Name | trim | glink | glinkInc }}@) 16 | @{{- range $ri, $response := $item.Responses }}@ 17 | @{{- $riRoman := ($ri | addOne | roman) }}@ 18 | 1. [@{{ $response.Name | trim }}@](#@{{ (print $riRoman ". Example Request: " $response.Name) | trim | glink | glinkInc }}@) 19 | @{{- /* End iterate responses for the request */}}@ 20 | @{{- end }}@ 21 | @{{- /* End iterate requests in the collection */}}@ 22 | @{{- end }}@ 23 | @{{- /* End iterate collections */}}@ 24 | @{{- end }}@ 25 | @{{- /* End if we have more than one collection */}}@ 26 | @{{- end }}@ 27 | 28 | 29 | @{{ if .Data.Variables }}@ 30 | ## Variables 31 | 32 | 33 | | Key | Value | Type | 34 | | --- | ------|-------------| 35 | @{{ range $ih, $v := .Data.Variables -}}@ 36 | | @{{ $v.Key }}@ | @{{ $v.Value }}@ | @{{ $v.Type }}@ | 37 | @{{ end }}@ 38 | 39 | 40 | 41 | @{{ end }}@ 42 | 43 | 44 | ## Endpoints 45 | 46 | 47 | @{{- if gt $numCollections 1 }}@ 48 | @{{- range $index, $c := .Data.Collections }}@ 49 | * [@{{ $c.Name | trim }}@](#@{{ $c.Name | trim | glink }}@) 50 | @{{- range $i, $item := $c.Items }}@ 51 | 1. [@{{ $item.Name | trim }}@](#@{{ merge $i $item.Name | trim | glink | glinkInc }}@) 52 | @{{- range $ri, $response := $item.Responses }}@ 53 | @{{- $riRoman := ($ri | addOne | roman) }}@ 54 | * [@{{ $response.Name | trim }}@](#@{{ (print $riRoman ". Example Request: " $response.Name) | trim | glink | glinkInc }}@) 55 | @{{- /* End iterate responses for the request */}}@ 56 | @{{- end }}@ 57 | @{{- /* End iterate requests in the collection */}}@ 58 | @{{- end }}@ 59 | @{{- /* End iterate collections */}}@ 60 | @{{- end }}@ 61 | @{{- /* End if we have more than one collection */}}@ 62 | @{{- end }}@ 63 | 64 | -------- 65 | 66 | 67 | @{{ range $di, $d := .Data.Collections }}@ 68 | 69 | @{{ if gt $numCollections 1 }}@ 70 | ## @{{ $d.Name | trim }}@ 71 | @{{ end }}@ 72 | @{{ $d.Description }}@ 73 | 74 | 75 | 76 | @{{ range $ii, $item := $d.Items }}@ 77 | ### @{{ $ii | addOne }}@. @{{ if $item.Name }}@@{{ $item.Name | trim }}@@{{ end }}@ 78 | 79 | @{{ if $item.Request.Description }}@ 80 | @{{ $item.Request.Description }}@ 81 | @{{ end }}@ 82 | 83 | ***Endpoint:*** 84 | 85 | ```bash 86 | Method: @{{ $item.Request.Method | upper }}@ 87 | Type: @{{ $item.Request.Body.Mode | upper }}@ 88 | URL: @{{ $item.Request.URL.Raw | trimQueryParams | e }}@ 89 | ``` 90 | 91 | 92 | @{{ if $item.Request.Headers }}@ 93 | ***Headers:*** 94 | 95 | 96 | | Key | Value | Description | 97 | | --- | ------|-------------| 98 | @{{ range $ih, $h := $item.Request.Headers -}}@ 99 | | @{{ $h.Key | e }}@ | @{{ $h.Value | e }}@ | @{{ $h.Description | e }}@ | 100 | @{{ end }}@ 101 | 102 | 103 | 104 | @{{ end }}@ 105 | 106 | 107 | @{{ if $item.Request.URL.Query }}@ 108 | ***Query params:*** 109 | 110 | 111 | | Key | Value | Description | 112 | | --- | ------|-------------| 113 | @{{ range $iq, $q := $item.Request.URL.Query -}}@ 114 | | @{{ $q.Key | e }}@ | @{{ $q.Value | e }}@ | @{{ $q.Description | e }}@ | 115 | @{{ end }}@ 116 | @{{ end }}@ 117 | 118 | 119 | 120 | @{{ if $item.Request.URL.Variables }}@ 121 | ***URL variables:*** 122 | 123 | 124 | | Key | Value | Description | 125 | | --- | ------|-------------| 126 | @{{ range $iq, $q := $item.Request.URL.Variables -}}@ 127 | | @{{ $q.Key | e }}@ | @{{ $q.Value | e }}@ | @{{ $q.Description | e }}@ | 128 | @{{ end }}@ 129 | @{{ end }}@ 130 | 131 | 132 | 133 | @{{ if $item.Request.Body.Mode}}@ 134 | 135 | @{{ if eq $item.Request.Body.Mode "raw"}}@ 136 | @{{ if $item.Request.Body.Raw }}@ 137 | ***Body:*** 138 | 139 | ```js 140 | @{{ $item.Request.Body.Raw }}@ 141 | ``` 142 | @{{ end }}@ 143 | @{{ end }}@ 144 | 145 | 146 | 147 | @{{ if eq $item.Request.Body.Mode "formdata"}}@ 148 | 149 | @{{ if $item.Request.Body.FormData }}@ 150 | ***Body:*** 151 | 152 | | Key | Value | Description | 153 | | --- | ------|-------------| 154 | @{{ range $if, $f := $item.Request.Body.FormData -}}@ 155 | | @{{ $f.Key | e }}@ | @{{ $f.Value | e }}@ | @{{ $f.Description | e }}@ | 156 | @{{ end }}@ 157 | @{{ end }}@ 158 | @{{ end }}@ 159 | 160 | 161 | 162 | 163 | @{{ if eq $item.Request.Body.Mode "urlencoded"}}@ 164 | ***Body:*** 165 | 166 | @{{ if $item.Request.Body.URLEncoded }}@ 167 | | Key | Value | Description | 168 | | --- | ------|-------------| 169 | @{{ range $iu, $u := $item.Request.Body.URLEncoded -}}@ 170 | | @{{ $u.Key | e }}@ | @{{ $u.Value | e }}@ | @{{ $u.Description | e }}@ | 171 | @{{ end }}@ 172 | @{{ end }}@ 173 | @{{ end }}@ 174 | 175 | 176 | 177 | @{{ end }}@ 178 | 179 | 180 | @{{ if $item.Responses }}@ 181 | ***More example Requests/Responses:*** 182 | @{{ range $ir, $resp := $item.Responses }}@ 183 | @{{ if $resp.Name }}@ 184 | #### @{{ $ir | addOne | roman }}@. Example Request: @{{ $resp.Name }}@ 185 | 186 | 187 | @{{ if $resp.OriginalRequest.Headers }}@ 188 | ***Headers:*** 189 | 190 | 191 | | Key | Value | Description | 192 | | --- | ------|-------------| 193 | @{{ range $ih, $h := $resp.OriginalRequest.Headers -}}@ 194 | | @{{ $h.Key | e }}@ | @{{ $h.Value | e }}@ | @{{ $h.Description | e }}@ | 195 | @{{ end }}@ 196 | 197 | 198 | 199 | @{{ end }}@ 200 | 201 | 202 | 203 | @{{ if $resp.OriginalRequest.URL.Query }}@ 204 | ***Query:*** 205 | 206 | 207 | | Key | Value | Description | 208 | | --- | ------|-------------| 209 | @{{ range $ih, $h := $resp.OriginalRequest.URL.Query -}}@ 210 | | @{{ $h.Key | e }}@ | @{{ $h.Value | e }}@ | @{{ $h.Description | e }}@ | 211 | @{{ end }}@ 212 | 213 | 214 | 215 | @{{ end }}@ 216 | 217 | 218 | @{{ if $resp.OriginalRequest.URL.Variables }}@ 219 | ***Query:*** 220 | 221 | 222 | | Key | Value | Description | 223 | | --- | ------|-------------| 224 | @{{ range $ih, $h := $resp.OriginalRequest.URL.Variables -}}@ 225 | | @{{ $h.Key | e }}@ | @{{ $h.Value | e }}@ | @{{ $h.Description | e }}@ | 226 | @{{ end }}@ 227 | 228 | 229 | 230 | @{{ end }}@ 231 | 232 | 233 | @{{ if ne $resp.OriginalRequest.Body.Mode "" }}@ 234 | 235 | @{{ if eq $resp.OriginalRequest.Body.Mode "raw"}}@ 236 | @{{ if $resp.OriginalRequest.Body.Raw }}@ 237 | ***Body:*** 238 | 239 | ```js 240 | @{{ $resp.OriginalRequest.Body.Raw }}@ 241 | ``` 242 | @{{ end }}@ 243 | @{{ end }}@ 244 | 245 | 246 | 247 | @{{ if eq $resp.OriginalRequest.Body.Mode "formdata"}}@ 248 | 249 | @{{ if $resp.OriginalRequest.Body.FormData }}@ 250 | ***Body:*** 251 | 252 | | Key | Value | Description | 253 | | --- | ------|-------------| 254 | @{{ range $if, $f := $resp.OriginalRequest.Body.FormData -}}@ 255 | | @{{ $f.Key | e }}@ | @{{ $f.Value | e }}@ | @{{ $f.Description | e }}@ | 256 | @{{ end }}@ 257 | @{{ end }}@ 258 | @{{ end }}@ 259 | 260 | 261 | 262 | 263 | @{{ if eq $item.Request.Body.Mode "urlencoded"}}@ 264 | ***Body:*** 265 | 266 | @{{ if $resp.OriginalRequest.Body.URLEncoded }}@ 267 | | Key | Value | Description | 268 | | --- | ------|-------------| 269 | @{{ range $iu, $u := $resp.OriginalRequest.Body.URLEncoded -}}@ 270 | | @{{ $u.Key | e }}@ | @{{ $u.Value | e }}@ | @{{ $u.Description | e }}@ | 271 | @{{ end }}@ 272 | @{{ end }}@ 273 | @{{ end }}@ 274 | 275 | 276 | @{{ else }}@ 277 | ***Body: None*** 278 | 279 | @{{ end }}@ 280 | 281 | @{{ if $resp.Body }}@ 282 | #### @{{ $ir | addOne | roman }}@. Example Response: @{{ $resp.Name }}@ 283 | ```js 284 | @{{ $resp.Body }}@ 285 | ``` 286 | @{{ end }}@ 287 | 288 | ***Status Code:*** @{{ $resp.Code }}@ 289 | 290 |
291 | 292 | 293 | @{{ end }}@ 294 | 295 | 296 | @{{ end }}@ 297 | 298 | 299 | @{{ end }}@ 300 | 301 | 302 | @{{ end }}@ 303 | 304 | 305 | @{{ end }}@ 306 | 307 | 308 | --- 309 | [Back to top](#@{{ .Data.Info.Name | trim | glink }}@) 310 | 311 | >Generated at @{{date_time}}@ by [docgen](https://github.com/thedevsaddam/docgen) 312 | -------------------------------------------------------------------------------- /assets/markdown.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | @{{ if .Data.Info.Name }}@ 10 | @{{ .Data.Info.Name }}@ 11 | @{{ end }}@ 12 | @{{ if .Data.Info.Description }}@ 13 |  |  14 | @{{ .Data.Info.Description }}@ 15 | @{{ end }}@ 16 | 17 | 18 | 24 | 25 | 26 | 30 | 44 | 45 | 46 |
47 | @{{ .MarkdownHTML | html }}@ 48 |
49 | 50 | 51 | -------------------------------------------------------------------------------- /assets/scripts.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){ 2 | // Add minus icon for collapse element which is open by default 3 | $(".collapse.in").each(function(){ 4 | $(this).siblings(".panel-heading").find(".glyphicon").addClass("glyphicon-minus").removeClass("glyphicon-plus"); 5 | }); 6 | 7 | // Toggle plus minus icon on show hide of collapse element 8 | $(".collapse").on('show.bs.collapse', function(){ 9 | $(this).parent().find(".glyphicon").removeClass("glyphicon-plus").addClass("glyphicon-minus"); 10 | }).on('hide.bs.collapse', function(){ 11 | $(this).parent().find(".glyphicon").removeClass("glyphicon-minus").addClass("glyphicon-plus"); 12 | }); 13 | 14 | 15 | $('.resp-prettyprint').each(function() { 16 | var ctx = $(this); 17 | var html = ctx.html(); 18 | ctx.html(""); 19 | 20 | html = html.replaceAll('\\n', ''); 21 | html = html.replaceAll('\\t', ''); 22 | html = html.replaceAll('\\', ''); 23 | html = html.trim(); 24 | 25 | if (html.charAt(0) === '"'){ 26 | html = html.substr(1); 27 | html = html.slice(0, -1); 28 | } 29 | if (IsJsonString(html)){ 30 | var obj = JSON.parse(html); 31 | var formattedJson = JSON.stringify(obj, null, 4); 32 | ctx.html("
" + syntaxHighlight(formattedJson) + "
"); 33 | } else { 34 | ctx.html("
" + escapeHtml(html) + "
"); 35 | } 36 | }); 37 | 38 | $(".resp-selector").change(function () { 39 | $(this).find("option").map(function(){ 40 | $("#"+this.value).hide() 41 | }); 42 | var $option = $(this).find('option:selected'); 43 | $("#"+$option.val()).show() 44 | }); 45 | 46 | $('[data-toggle="tooltip"]').tooltip(); 47 | }); 48 | 49 | function escapeHtml(text) { 50 | var map = { 51 | '&': '&', 52 | '<': '<', 53 | '>': '>', 54 | '"': '"', 55 | "'": ''' 56 | }; 57 | 58 | return text.replace(/[&<>"']/g, function(m) { return map[m]; }); 59 | } 60 | 61 | function IsJsonString(str) { 62 | try { 63 | JSON.parse(str); 64 | } catch (e) { 65 | return false; 66 | } 67 | return true; 68 | } 69 | 70 | String.prototype.replaceAll = function (replaceThis, withThis) { 71 | var re = new RegExp(RegExp.quote(replaceThis),"g"); 72 | return this.replace(re, withThis); 73 | }; 74 | 75 | 76 | RegExp.quote = function(str) { 77 | return str.replace(/([.?*+^$[\]\\(){}-])/g, "\\$1"); 78 | }; 79 | 80 | function syntaxHighlight(json) { 81 | json = json.replace(/&/g, '&').replace(//g, '>'); 82 | return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) { 83 | var cls = 'number'; 84 | if (/^"/.test(match)) { 85 | if (/:$/.test(match)) { 86 | cls = 'key'; 87 | } else { 88 | cls = 'string'; 89 | } 90 | } else if (/true|false/.test(match)) { 91 | cls = 'boolean'; 92 | } else if (/null/.test(match)) { 93 | cls = 'null'; 94 | } 95 | return '' + match + ''; 96 | }); 97 | } 98 | 99 | 100 | -------------------------------------------------------------------------------- /assets/styles.css: -------------------------------------------------------------------------------- 1 | body{ 2 | color: #555555 !important; 3 | letter-spacing: 0.25px !important; 4 | } 5 | .panel-default>.panel-heading { 6 | color: #333; 7 | background-color: #ffffff; 8 | border-color: #efefef; 9 | } 10 | .request-item .panel-title{ 11 | font-size: 12px !important; 12 | } 13 | .panel-title .glyphicon{ 14 | font-size: 12px; 15 | font-weight: bold; 16 | } 17 | .collection-intro{ 18 | padding-bottom: 10px !important; 19 | } 20 | ul{ 21 | list-style: none; 22 | } 23 | ul li{ 24 | margin-left: -30px; 25 | line-height: 16pt; 26 | } 27 | ul li .endpoint_menu{ 28 | -webkit-transition: all 0.5s ease-out; 29 | -moz-transition: all 0.5s ease-out; 30 | -o-transition: all 0.5s ease-out; 31 | transition: all 0.5s ease-out; 32 | } 33 | ul li .endpoint_menu:hover{ 34 | margin-left: 5px; 35 | font-weight: bolder; 36 | } 37 | a{ 38 | text-decoration: none !important; 39 | } 40 | .request-method{ 41 | padding: 4px; 42 | } 43 | .border-success{ 44 | border: 1px solid #3c763d; 45 | } 46 | .border-info{ 47 | border: 1px solid #337ab7; 48 | } 49 | .border-warning{ 50 | border: 1px solid #f0ad4e; 51 | } 52 | .border-danger{ 53 | border: 1px solid #a94442; 54 | } 55 | pre {outline: 1px solid #ffffff; padding: 5px; margin: 5px; } 56 | .string { color: #16a085; } 57 | .number { color: #f39c12; } 58 | .boolean { color: #2980b9; } 59 | .null { color: magenta; } 60 | .key { color: #e74c3c; } 61 | .null { color: #8e44ad; } 62 | .love-color{ 63 | color: #c0392b; 64 | font-weight: bold; 65 | } 66 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | GIT_COMMIT=$(git rev-parse --short HEAD) 4 | TAG=$(git describe --exact-match --abbrev=0 --tags ${COMMIT} 2> /dev/null || true) 5 | DATE=$(date +'%Y-%m-%d') 6 | 7 | echo "Building binaries" 8 | echo Git commit: $GIT_COMMIT Version: $TAG Build date: $DATE 9 | 10 | go generate 11 | 12 | # MAC 13 | export GOARCH="amd64" 14 | export GOOS="darwin" 15 | export CGO_ENABLED=0 16 | go build -ldflags "-X github.com/thedevsaddam/docgen/cmd.GitCommit=$GIT_COMMIT -X github.com/thedevsaddam/docgen/cmd.Version=$TAG -X github.com/thedevsaddam/docgen/cmd.BuildDate=$DATE" -o bin/darwin_amd64 -v . 17 | 18 | export GOARCH="arm64" 19 | export GOOS="darwin" 20 | export CGO_ENABLED=0 21 | go build -ldflags "-X github.com/thedevsaddam/docgen/cmd.GitCommit=$GIT_COMMIT -X github.com/thedevsaddam/docgen/cmd.Version=$TAG -X github.com/thedevsaddam/docgen/cmd.BuildDate=$DATE" -o bin/darwin_arm64 -v . 22 | 23 | #LINUX 24 | export GOARCH="amd64" 25 | export GOOS="linux" 26 | export CGO_ENABLED=0 27 | go build -ldflags "-X github.com/thedevsaddam/docgen/cmd.GitCommit=$GIT_COMMIT -X github.com/thedevsaddam/docgen/cmd.Version=$TAG -X github.com/thedevsaddam/docgen/cmd.BuildDate=$DATE" -o bin/linux_amd64 -v 28 | 29 | export GOARCH="arm64" 30 | export GOOS="linux" 31 | export CGO_ENABLED=0 32 | go build -ldflags "-X github.com/thedevsaddam/docgen/cmd.GitCommit=$GIT_COMMIT -X github.com/thedevsaddam/docgen/cmd.Version=$TAG -X github.com/thedevsaddam/docgen/cmd.BuildDate=$DATE" -o bin/linux_arm64 -v 33 | 34 | export GOARCH="386" 35 | export GOOS="linux" 36 | export CGO_ENABLED=0 37 | go build -ldflags "-X github.com/thedevsaddam/docgen/cmd.GitCommit=$GIT_COMMIT -X github.com/thedevsaddam/docgen/cmd.Version=$TAG -X github.com/thedevsaddam/docgen/cmd.BuildDate=$DATE" -o bin/linux_386 -v 38 | 39 | #WINDOWS 40 | export GOARCH="386" 41 | export GOOS="windows" 42 | export CGO_ENABLED=0 43 | go build -ldflags "-X github.com/thedevsaddam/docgen/cmd.GitCommit=$GIT_COMMIT -X github.com/thedevsaddam/docgen/cmd.Version=$TAG -X github.com/thedevsaddam/docgen/cmd.BuildDate=$DATE" -o bin/windows_386.exe -v 44 | 45 | export GOARCH="amd64" 46 | export GOOS="windows" 47 | export CGO_ENABLED=0 48 | go build -ldflags "-X github.com/thedevsaddam/docgen/cmd.GitCommit=$GIT_COMMIT -X github.com/thedevsaddam/docgen/cmd.Version=$TAG -X github.com/thedevsaddam/docgen/cmd.BuildDate=$DATE" -o bin/windows_amd64.exe -v 49 | 50 | echo "Build complete" -------------------------------------------------------------------------------- /cmd/build-html.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "io/ioutil" 5 | "log" 6 | "os" 7 | "strings" 8 | 9 | "github.com/spf13/cobra" 10 | "github.com/tdewolff/minify" 11 | mcss "github.com/tdewolff/minify/css" 12 | mhtml "github.com/tdewolff/minify/html" 13 | mjs "github.com/tdewolff/minify/js" 14 | ) 15 | 16 | func buildAndGenerateHTMLFile(cmd *cobra.Command, args []string) { 17 | if in == "" { 18 | log.Println("You must provide a input file name!") 19 | return 20 | } 21 | 22 | if out == "" { 23 | log.Println("You must provide a output file name!") 24 | return 25 | } 26 | if _, err := os.Stat(in); os.IsNotExist(err) { 27 | log.Println("Invalid file path!") 28 | return 29 | } 30 | buf := readJSONtoHTML(in) 31 | if !strings.HasSuffix(out, ".html") { 32 | out = out + ".html" 33 | } 34 | m := minify.New() 35 | m.AddFunc("text/html", mhtml.Minify) 36 | m.AddFunc("text/css", mcss.Minify) 37 | m.AddFunc("text/javascript", mjs.Minify) 38 | b, err := m.Bytes("text/html;text/css;text/javascript", buf.Bytes()) 39 | if err != nil { 40 | panic(err) 41 | } 42 | if err := ioutil.WriteFile(out, b, 0644); err != nil { 43 | log.Fatal(err) 44 | } 45 | log.Printf("Documentation successfully generated to %s", out) 46 | } 47 | -------------------------------------------------------------------------------- /cmd/build-markdown.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "io/ioutil" 5 | "log" 6 | "os" 7 | "strings" 8 | 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | func buildAndGenerateMarkdownFile(cmd *cobra.Command, args []string) { 13 | if in == "" { 14 | log.Println("You must provide a input file name!") 15 | return 16 | } 17 | 18 | if out == "" { 19 | log.Println("You must provide a output file name!") 20 | return 21 | } 22 | if _, err := os.Stat(in); os.IsNotExist(err) { 23 | log.Println("Invalid file path!") 24 | return 25 | } 26 | buf := readJSONtoMarkdown(in) 27 | if !strings.HasSuffix(out, ".md") { 28 | out = out + ".md" 29 | } 30 | 31 | lines := strings.Split(buf.String(), "\n") 32 | for i, l := range lines { 33 | if strings.HasPrefix(l, "") { 34 | lines = append(lines[:i], lines[i+1:]...) 35 | } 36 | } 37 | 38 | // contents := strings.Join(lines, "\n") 39 | var contents string 40 | var ws int 41 | for _, l := range lines { 42 | if l == "" { 43 | ws++ 44 | } else { 45 | ws = 0 46 | } 47 | if ws <= 3 { 48 | contents += "\n" + l 49 | } 50 | } 51 | 52 | if err := ioutil.WriteFile(out, []byte(contents), 0644); err != nil { 53 | log.Fatal(err) 54 | } 55 | log.Printf("Documentation successfully generated to %s", out) 56 | } 57 | -------------------------------------------------------------------------------- /cmd/build.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/spf13/cobra" 7 | "github.com/thedevsaddam/docgen/collection" 8 | ) 9 | 10 | var ( 11 | in string 12 | out string 13 | env string 14 | isMarkdown bool 15 | includeVariable bool 16 | sorted bool 17 | envCollection = &collection.Environment{} 18 | buildOutput = &cobra.Command{ 19 | Use: "build", 20 | Short: "Build html/markdown documentation from postman collection", 21 | Long: `Build html/markdown documentation from postman collection`, 22 | Run: buildAngGenerateFile, 23 | } 24 | ) 25 | 26 | func init() { 27 | buildOutput.PersistentFlags().StringVarP(&in, "in", "i", "", "postman collection path") 28 | buildOutput.PersistentFlags().StringVarP(&out, "out", "o", "", "output file path") 29 | buildOutput.PersistentFlags().BoolVarP(&isMarkdown, "md", "m", false, "this flag will command to generate markdown") 30 | buildOutput.PersistentFlags().BoolVarP(&includeVariable, "var", "v", false, "this flag will include variables in template") 31 | buildOutput.PersistentFlags().BoolVarP(&sorted, "sort", "s", false, "this flag will sort the collection in ascending order") 32 | buildOutput.PersistentFlags().StringVarP(&extraCSS, "css", "c", "", "inject a css file") 33 | buildOutput.PersistentFlags().StringVarP(&env, "env", "e", "", "postman environment variable file path") 34 | } 35 | 36 | func buildAngGenerateFile(cmd *cobra.Command, args []string) { 37 | if env != "" { 38 | if _, err := os.Stat(env); os.IsNotExist(err) { 39 | handleErr("Invalid environment file path", err) 40 | } 41 | f, err := os.Open(env) 42 | if err != nil { 43 | handleErr("Unable to open file", err) 44 | } 45 | if err := envCollection.Open(f); err != nil { 46 | handleErr("Unable to parse environment file", err) 47 | } 48 | } 49 | if isMarkdown { 50 | buildAndGenerateMarkdownFile(cmd, args) 51 | return 52 | } 53 | buildAndGenerateHTMLFile(cmd, args) 54 | } 55 | -------------------------------------------------------------------------------- /cmd/funcmap.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "html" 6 | "html/template" 7 | "io/ioutil" 8 | "log" 9 | "net/url" 10 | "regexp" 11 | "strconv" 12 | "strings" 13 | "time" 14 | 15 | "github.com/thedevsaddam/docgen/assets_bin" 16 | "gopkg.in/russross/blackfriday.v2" 17 | ) 18 | 19 | func getData(a string) string { 20 | fp, err := assets_bin.AssetFS.Open(a) 21 | if err != nil { 22 | log.Fatal(err) 23 | } 24 | bs, err := ioutil.ReadAll(fp) 25 | if err != nil { 26 | log.Fatal(err) 27 | } 28 | return string(bs) 29 | } 30 | 31 | func eHTML(s string) string { 32 | return html.EscapeString(s) 33 | } 34 | 35 | func htmlTemplate(v string) template.HTML { 36 | return template.HTML(v) 37 | } 38 | 39 | func cssTemplate(v string) template.CSS { 40 | return template.CSS(v) 41 | } 42 | 43 | func jsTemplate(v string) template.JS { 44 | return template.JS(v) 45 | } 46 | 47 | func snake(v string) string { 48 | 49 | reg, err := regexp.Compile("[^a-zA-Z0-9%]+") 50 | resURI := url.QueryEscape(v) 51 | if err != nil { 52 | 53 | log.Fatal(err) 54 | } 55 | result := reg.ReplaceAllString(resURI, "") 56 | result = strings.Replace(result, "%", "_", -1) 57 | return result 58 | } 59 | 60 | func trimQueryParams(v string) string { 61 | if strings.Contains(v, "?") { 62 | return strings.Split(v, "?")[0] 63 | } 64 | return v 65 | } 66 | 67 | func addOne(v int) string { 68 | return strconv.Itoa(v + 1) 69 | } 70 | 71 | func trim(v string) string { 72 | return strings.TrimSpace(v) 73 | } 74 | 75 | func lower(v string) string { 76 | return strings.ToLower(v) 77 | } 78 | 79 | func upper(v string) string { 80 | return strings.ToUpper(v) 81 | } 82 | 83 | func githubLink(v string) string { 84 | v = strings.ToLower(v) 85 | 86 | v = strings.Replace(v, " ", "-", -1) 87 | nonWordChars := regexp.MustCompile(`[^-\w\d]`) 88 | v = nonWordChars.ReplaceAllString(v, "") 89 | return v 90 | } 91 | 92 | func githubLinkIncrementer(v string) string { 93 | k, ok := githubLinkInc[v] 94 | if ok { 95 | githubLinkInc[v]++ 96 | return v + "-" + strconv.Itoa((k + 1)) 97 | } 98 | githubLinkInc[v] = 0 99 | return v 100 | } 101 | 102 | func merge(v1 int, v2 string) string { 103 | return strconv.Itoa(v1+1) + ". " + v2 104 | } 105 | 106 | func markdown(v string) string { 107 | return string(blackfriday.Run([]byte(v))) 108 | } 109 | 110 | func dateTime() string { 111 | return time.Now().Format("2006-01-02 15:04:05") 112 | } 113 | 114 | func color(v string) string { 115 | switch v { 116 | case "GET": 117 | return "info" 118 | case "POST": 119 | return "success" 120 | case "PATCH": 121 | return "warning" 122 | case "PUT": 123 | return "warning" 124 | case "DELETE": 125 | return "danger" 126 | default: 127 | return "info" 128 | 129 | } 130 | } 131 | 132 | // integer to roman number 133 | func roman(num string) string { 134 | number, _ := strconv.Atoi(num) 135 | conversions := []struct { 136 | value int 137 | digit string 138 | }{ 139 | {1000, "M"}, 140 | {900, "CM"}, 141 | {500, "D"}, 142 | {400, "CD"}, 143 | {100, "C"}, 144 | {90, "XC"}, 145 | {50, "L"}, 146 | {40, "XL"}, 147 | {10, "X"}, 148 | {9, "IX"}, 149 | {5, "V"}, 150 | {4, "IV"}, 151 | {1, "I"}, 152 | } 153 | 154 | roman := "" 155 | for _, conversion := range conversions { 156 | for number >= conversion.value { 157 | roman += conversion.digit 158 | number -= conversion.value 159 | } 160 | } 161 | return roman 162 | } 163 | 164 | // provide env values 165 | func e(key string) string { 166 | for _, k := range envCollection.Values { 167 | key = strings.ReplaceAll(key, fmt.Sprintf("{{%s}}", k.Key), k.Value) 168 | } 169 | return key 170 | } 171 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "os" 10 | "text/template" 11 | 12 | "github.com/spf13/cobra" 13 | "github.com/thedevsaddam/docgen/collection" 14 | "github.com/thedevsaddam/docgen/update" 15 | ) 16 | 17 | const logo = ` 18 | _____ _____ 19 | | __ \ / ____| 20 | | | | | ___ ___ | | __ ___ _ __ 21 | | | | |/ _ \ / __| | | |_ |/ _ \ '_ \ 22 | | |__| | (_) | (__ | |__| | __/ | | | 23 | |_____/ \___/ \___| \_____|\___|_| |_| 24 | 25 | Generate API documentation from Postman JSON collection 26 | For more info visit: https://github.com/thedevsaddam/docgen 27 | ` 28 | 29 | var ( 30 | assets Assets 31 | 32 | githubLinkInc = make(map[string]int) 33 | 34 | extraCSS string 35 | 36 | GitCommit = "unknown" 37 | Version = "unknown" 38 | BuildDate = "unknown" 39 | 40 | cmd = &cobra.Command{ 41 | Use: "docgen", 42 | Short: "Generate documentation from Postman JSON collection", 43 | Long: logo, 44 | Run: rootFunc, 45 | } 46 | ) 47 | 48 | // Assets represents template assets 49 | type Assets struct { 50 | BootstrapJS string 51 | BootstrapCSS string 52 | IndexHTML string 53 | JqueryJS string 54 | ScriptsJS string 55 | StylesCSS string 56 | ExtraCSS string 57 | 58 | IndexMarkdown string 59 | MarkdownHTML string 60 | GithubMarkdownMinCSS string 61 | } 62 | 63 | func init() { 64 | // init assets 65 | assets = Assets{ 66 | IndexHTML: getData("index.html"), 67 | 68 | BootstrapJS: getData("bootstrap.min.js"), 69 | BootstrapCSS: getData("bootstrap.min.css"), 70 | 71 | JqueryJS: getData("jquery.min.js"), 72 | ScriptsJS: getData("scripts.js"), 73 | StylesCSS: getData("styles.css"), 74 | 75 | IndexMarkdown: getData("index.md"), 76 | MarkdownHTML: getData("markdown.html"), 77 | GithubMarkdownMinCSS: getData("github-markdown.min.css"), 78 | } 79 | 80 | // register commands 81 | cmd.AddCommand(versionCmd) 82 | cmd.AddCommand(serveLive) 83 | cmd.AddCommand(buildOutput) 84 | } 85 | 86 | // Execute the root command 87 | func Execute() error { 88 | return cmd.Execute() 89 | } 90 | 91 | func rootFunc(cmd *cobra.Command, args []string) { 92 | err := update.SelfUpdate(context.Background(), BuildDate, Version) 93 | if err != nil { 94 | fmt.Println("Error: failed to update docgen:", err) //this error can be skipped 95 | } 96 | } 97 | 98 | func handleErr(message string, err error) { 99 | if err != nil { 100 | log.Fatal(message, err) 101 | } 102 | } 103 | 104 | func readJSONtoHTML(str string) *bytes.Buffer { 105 | var rt collection.Documentation 106 | f, err := os.Open(str) 107 | if err != nil { 108 | log.Fatal("opening file", err.Error()) 109 | } 110 | 111 | // open and build collection 112 | if err = rt.Open(f); err != nil { 113 | log.Fatal("parsing json file", err.Error()) 114 | } 115 | 116 | // populate envCollection with collection variables 117 | if len(rt.Variables) > 0 && includeVariable { 118 | envCollection.SetCollectionVariables(rt.Variables) 119 | } 120 | 121 | if sorted { 122 | rt.SortCollections() 123 | } 124 | 125 | // override collection variables by env variables 126 | if env != "" { 127 | if _, err := os.Stat(env); os.IsNotExist(err) { 128 | handleErr("Invalid environment file path", err) 129 | } 130 | f, err := os.Open(env) 131 | if err != nil { 132 | handleErr("Unable to open file", err) 133 | } 134 | if err := envCollection.Open(f); err != nil { 135 | handleErr("Unable to parse environment file", err) 136 | } 137 | } 138 | 139 | tm := template.New("main") 140 | tm.Delims("@{{", "}}@") 141 | tm.Funcs(template.FuncMap{ 142 | "html": htmlTemplate, 143 | "css": cssTemplate, 144 | "js": jsTemplate, 145 | "eHTML": eHTML, 146 | "snake": snake, 147 | "addOne": addOne, 148 | "color": color, 149 | "trimQueryParams": trimQueryParams, 150 | "date_time": dateTime, 151 | "markdown": markdown, 152 | "e": e, 153 | }) 154 | t, err := tm.Parse(assets.IndexHTML) 155 | if err != nil { 156 | log.Fatal(err) 157 | } 158 | 159 | if extraCSS != "" { 160 | ecFile, err := ioutil.ReadFile(extraCSS) 161 | if err != nil { 162 | log.Fatal("css file", err.Error()) 163 | } 164 | assets.ExtraCSS = string(ecFile) 165 | } 166 | 167 | data := struct { 168 | Assets Assets 169 | Data collection.Documentation 170 | }{ 171 | Assets: assets, 172 | Data: rt, 173 | } 174 | buf := new(bytes.Buffer) 175 | // defer buf.Reset() 176 | if err := t.Execute(buf, data); err != nil { 177 | log.Fatal(err) 178 | } 179 | return buf 180 | } 181 | func readJSONtoMarkdown(str string) *bytes.Buffer { 182 | var rt collection.Documentation 183 | f, err := os.Open(str) 184 | if err != nil { 185 | log.Fatal("opening file", err.Error()) 186 | } 187 | 188 | if err = rt.Open(f); err != nil { 189 | log.Fatal("parsing json file", err.Error()) 190 | } 191 | 192 | // populate envCollection with collection variables 193 | if len(rt.Variables) > 0 && includeVariable { 194 | envCollection.SetCollectionVariables(rt.Variables) 195 | } 196 | 197 | if sorted { 198 | rt.SortCollections() 199 | } 200 | 201 | // override collection variables by env variables 202 | if env != "" { 203 | if _, err := os.Stat(env); os.IsNotExist(err) { 204 | handleErr("Invalid environment file path", err) 205 | } 206 | f, err := os.Open(env) 207 | if err != nil { 208 | handleErr("Unable to open file", err) 209 | } 210 | if err := envCollection.Open(f); err != nil { 211 | handleErr("Unable to parse environment file", err) 212 | } 213 | } 214 | 215 | tm := template.New("main") 216 | tm.Delims("@{{", "}}@") 217 | tm.Funcs(template.FuncMap{ 218 | "snake": snake, 219 | "addOne": addOne, 220 | "trim": trim, 221 | "lower": lower, 222 | "upper": upper, 223 | "glink": githubLink, 224 | "glinkInc": githubLinkIncrementer, 225 | "merge": merge, 226 | "roman": roman, 227 | "date_time": dateTime, 228 | "trimQueryParams": trimQueryParams, 229 | "e": e, 230 | }) 231 | t, err := tm.Parse(assets.IndexMarkdown) 232 | if err != nil { 233 | log.Fatal(err) 234 | } 235 | data := struct { 236 | Data collection.Documentation 237 | }{ 238 | Data: rt, 239 | } 240 | buf := new(bytes.Buffer) 241 | // defer buf.Reset() 242 | if err := t.Execute(buf, data); err != nil { 243 | log.Fatal(err) 244 | } 245 | return buf 246 | } 247 | func readJSONtoMarkdownHTML(str string) *bytes.Buffer { 248 | // reset the link map 249 | githubLinkInc = make(map[string]int) 250 | var rt collection.Documentation 251 | f, err := os.Open(str) 252 | if err != nil { 253 | log.Fatal("opening file", err.Error()) 254 | } 255 | if err = rt.Open(f); err != nil { 256 | log.Fatal("parsing json file", err.Error()) 257 | } 258 | 259 | // populate envCollection with collection variables 260 | if len(rt.Variables) > 0 && includeVariable { 261 | envCollection.SetCollectionVariables(rt.Variables) 262 | } 263 | 264 | if sorted { 265 | rt.SortCollections() 266 | } 267 | 268 | // override collection variables by env variables 269 | if env != "" { 270 | if _, err := os.Stat(env); os.IsNotExist(err) { 271 | handleErr("Invalid environment file path", err) 272 | } 273 | f, err := os.Open(env) 274 | if err != nil { 275 | handleErr("Unable to open file", err) 276 | } 277 | if err := envCollection.Open(f); err != nil { 278 | handleErr("Unable to parse environment file", err) 279 | } 280 | } 281 | 282 | tm := template.New("main") 283 | tm.Delims("@{{", "}}@") 284 | tm.Funcs(template.FuncMap{ 285 | "html": htmlTemplate, 286 | "css": cssTemplate, 287 | "js": jsTemplate, 288 | "eHTML": eHTML, 289 | "snake": snake, 290 | "addOne": addOne, 291 | "color": color, 292 | "trimQueryParams": trimQueryParams, 293 | "date_time": dateTime, 294 | "markdown": markdown, 295 | "e": e, 296 | }) 297 | 298 | t, err := tm.Parse(assets.MarkdownHTML) 299 | if err != nil { 300 | log.Fatal(err) 301 | } 302 | 303 | buf := readJSONtoMarkdown(file) 304 | mdHTML := markdown(buf.String()) 305 | 306 | if extraCSS != "" { 307 | ecFile, err := ioutil.ReadFile(extraCSS) 308 | if err != nil { 309 | log.Fatal("css file", err.Error()) 310 | } 311 | assets.ExtraCSS = string(ecFile) 312 | } 313 | 314 | data := struct { 315 | Assets Assets 316 | Data collection.Documentation 317 | MarkdownHTML string 318 | }{ 319 | Assets: assets, 320 | Data: rt, 321 | MarkdownHTML: mdHTML, 322 | } 323 | buf.Reset() 324 | if err := t.Execute(buf, data); err != nil { 325 | log.Fatal(err) 326 | } 327 | return buf 328 | } 329 | -------------------------------------------------------------------------------- /cmd/server.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | "os" 9 | "strconv" 10 | 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | var ( 15 | file string 16 | port int 17 | serveLive = &cobra.Command{ 18 | Use: "server", 19 | Short: "Serve live html from postman collection", 20 | Long: `Serve live html from postman collection`, 21 | Run: server, 22 | } 23 | ) 24 | 25 | func init() { 26 | serveLive.PersistentFlags().IntVarP(&port, "port", "p", 9000, "port number to listen") 27 | serveLive.PersistentFlags().StringVarP(&file, "file", "f", "", "postman collection path") 28 | serveLive.PersistentFlags().BoolVarP(&isMarkdown, "md", "m", false, "display markdown format in preview") 29 | serveLive.PersistentFlags().BoolVarP(&includeVariable, "var", "v", false, "this flag will include variables in template") 30 | serveLive.PersistentFlags().BoolVarP(&sorted, "sort", "s", false, "this flag will sort the collection in ascending order") 31 | serveLive.PersistentFlags().StringVarP(&extraCSS, "css", "c", "", "inject a css file") 32 | serveLive.PersistentFlags().StringVarP(&env, "env", "e", "", "postman environment variable file path") 33 | } 34 | 35 | func server(cmd *cobra.Command, args []string) { 36 | if file == "" { 37 | log.Println("You must provide a file name!") 38 | return 39 | } 40 | if _, err := os.Stat(file); os.IsNotExist(err) { 41 | log.Println("Invalid file path!") 42 | return 43 | } 44 | http.HandleFunc("/", templateFunc) 45 | log.Println("Listening on port: ", port) 46 | log.Printf("Web Server is available at http://localhost:%s/\n", strconv.Itoa(port)) 47 | if err := http.ListenAndServe(fmt.Sprintf(":%d", port), nil); err != nil { 48 | handleErr("Failed to open server", err) 49 | } 50 | } 51 | 52 | func templateFunc(w http.ResponseWriter, r *http.Request) { 53 | w.Header().Add("Content-Type", "text/html") 54 | var buf *bytes.Buffer 55 | if !isMarkdown { 56 | buf = readJSONtoHTML(file) 57 | _, err := w.Write(buf.Bytes()) 58 | handleErr("Failed to write html in writer", err) 59 | return 60 | } 61 | buf = readJSONtoMarkdownHTML(file) 62 | _, err := w.Write(buf.Bytes()) 63 | handleErr("Failed to write html in writer", err) 64 | } 65 | -------------------------------------------------------------------------------- /cmd/version.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | var versionCmd = &cobra.Command{ 10 | Use: "version", 11 | Short: "Provide version information", 12 | Long: `Provide version information`, 13 | Run: func(cmd *cobra.Command, args []string) { 14 | fmt.Println(logo) 15 | fmt.Println("Version:", Version) 16 | fmt.Println("Git commit:", GitCommit) 17 | fmt.Println("Build date:", BuildDate) 18 | fmt.Println("Support postman collection version > 2.0") 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /collection/collection.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | "sort" 7 | ) 8 | 9 | var ( 10 | defaultCollection = "Ungrouped" 11 | ) 12 | 13 | type ( 14 | // Documentation describes the full API documentation 15 | Documentation struct { 16 | Info Info `json:"info"` 17 | Collections []Collection `json:"item"` 18 | Variables []Field `json:"variable"` 19 | } 20 | 21 | // Info describes the postman info section 22 | Info struct { 23 | Name string `json:"name"` 24 | Description string `json:"description"` 25 | Schema string `json:"schema"` 26 | } 27 | 28 | // Field describes query, header, form-data and urlencoded field of request 29 | Field struct { 30 | Key string `json:"key"` 31 | Value string `json:"value"` 32 | Description string `json:"description"` 33 | Type string `json:"type"` 34 | Disabled bool `json:"disabled"` 35 | Enabled bool `json:"enabled"` // this field is used by env 36 | } 37 | 38 | // URL describes URL of the request 39 | URL struct { 40 | Raw string `json:"raw"` 41 | Host []string `json:"host"` 42 | Path []string `json:"path"` 43 | Description string `json:"description"` 44 | Query []Field `json:"query"` 45 | Variables []Field `json:"variable"` 46 | } 47 | 48 | // Body describes a request body 49 | Body struct { 50 | Mode string `json:"mode"` 51 | FormData []Field `json:"formdata"` 52 | URLEncoded []Field `json:"urlencoded"` 53 | Raw string `json:"raw"` 54 | } 55 | 56 | // Request describes a request 57 | Request struct { 58 | Method string `json:"method"` 59 | Headers []Field `json:"header"` 60 | Body Body `json:"body"` 61 | URL URL `json:"url"` 62 | Description string `json:"description"` 63 | } 64 | 65 | // Response describes a request resposne 66 | Response struct { 67 | ID string `json:"id"` 68 | Name string `json:"name"` 69 | OriginalRequest Request `json:"originalRequest"` 70 | Status string `json:"status"` 71 | Code int `json:"code"` 72 | Headers []Field `json:"header"` 73 | Body string `json:"body"` 74 | PreviewLanguage string `json:"_postman_previewlanguage"` 75 | } 76 | 77 | // Item describes a request item 78 | Item struct { 79 | Name string `json:"name"` 80 | Items []Item `json:"item"` 81 | Request Request `json:"request"` 82 | Responses []Response `json:"response"` 83 | IsSubFolder bool `json:"_postman_isSubFolder"` 84 | } 85 | 86 | // Collection describes a request collection/item 87 | Collection struct { 88 | Name string `json:"name"` 89 | Description string `json:"description"` 90 | Items []Item `json:"item"` 91 | Item 92 | } 93 | ) 94 | 95 | // Open open a new collection reader 96 | func (d *Documentation) Open(rdr io.Reader) error { 97 | dcr := json.NewDecoder(rdr) 98 | if err := dcr.Decode(&d); err != nil { 99 | return err 100 | } 101 | 102 | // build the collections 103 | d.build() 104 | 105 | // remove empty collections 106 | d.removeEmptyCollections() 107 | // after building all the collections, remove disabled fields 108 | d.removeItemRequestDisabledField() 109 | d.removeItemResponseRequestDisabledField() 110 | 111 | // sort the collections in lexical order 112 | // d.sortCollections() 113 | 114 | return nil 115 | } 116 | 117 | // Build build UnCategorized collection 118 | func (d *Documentation) build() { 119 | c := Collection{} 120 | c.Name = defaultCollection 121 | for i := len(d.Collections) - 1; i >= 0; i-- { 122 | if len(d.Collections[i].Items) <= 0 { 123 | if d.Collections[i].Request.Method == "" { //a collection with no sub-items and request method is empty 124 | continue 125 | } 126 | c.Items = append(c.Items, Item{ 127 | Name: d.Collections[i].Name, 128 | Request: d.Collections[i].Request, 129 | Responses: d.Collections[i].Responses, 130 | }) 131 | d.Collections = append(d.Collections[:i], d.Collections[i+1:]...) 132 | } else { 133 | collection := Collection{} 134 | for j := len(d.Collections[i].Items) - 1; j >= 0; j-- { 135 | built := d.buildSubChildItems(d.Collections[i].Items[j], &collection, d.Collections[i].Name) 136 | if built { 137 | d.Collections[i].Items = append(d.Collections[i].Items[:j], d.Collections[i].Items[j+1:]...) //removing the sub folder from the parent to make it a collection itself 138 | } 139 | } 140 | } 141 | } 142 | d.Collections = append(d.Collections, c) 143 | } 144 | 145 | // buildSubChildItems builds all the sub folder collections 146 | func (d *Documentation) buildSubChildItems(itm Item, c *Collection, pn string) bool { 147 | if itm.IsSubFolder { 148 | collection := Collection{} 149 | collection.Name = pn + "/" + itm.Name 150 | collection.IsSubFolder = true 151 | for _, i := range itm.Items { 152 | d.buildSubChildItems(i, &collection, collection.Name) 153 | } 154 | d.Collections = append(d.Collections, collection) 155 | return true 156 | } 157 | c.Items = append(c.Items, Item{ 158 | Name: itm.Name, 159 | Request: itm.Request, 160 | Responses: itm.Responses, 161 | }) 162 | 163 | return false 164 | } 165 | 166 | // SortCollections sorts the collections in the alphabetical order(except for the default) 167 | func (d *Documentation) SortCollections() { 168 | sort.Slice(d.Collections, func(i int, j int) bool { 169 | if d.Collections[i].Name == defaultCollection { 170 | return false 171 | } 172 | return d.Collections[i].Name < d.Collections[j].Name 173 | }) 174 | 175 | for index := range d.Collections { 176 | sort.Slice(d.Collections[index].Items, func(i, j int) bool { 177 | return d.Collections[index].Items[i].Name < d.Collections[index].Items[j].Name 178 | }) 179 | } 180 | } 181 | 182 | //removeEmptyCollections removes any empty collection 183 | func (d *Documentation) removeEmptyCollections() { 184 | for i := 0; i < len(d.Collections); i++ { 185 | if len(d.Collections[i].Items) == 0 { 186 | d.Collections = append(d.Collections[:i], d.Collections[i+1:]...) //popping the certain empty collection 187 | d.removeEmptyCollections() //recursion followed by break to ensure proper indexing after a pop 188 | break 189 | } 190 | } 191 | return 192 | } 193 | 194 | //removeEmptyCollections removes disabled field from request 195 | func (d *Documentation) removeItemRequestDisabledField() { 196 | for i, c := range d.Collections { 197 | for j, _ := range c.Items { 198 | // remove disabled headers 199 | for k := len(d.Collections[i].Items[j].Request.Headers) - 1; k >= 0; k-- { 200 | if d.Collections[i].Items[j].Request.Headers[k].Disabled { 201 | d.Collections[i].Items[j].Request.Headers = append(d.Collections[i].Items[j].Request.Headers[:k], d.Collections[i].Items[j].Request.Headers[k+1:]...) 202 | } 203 | } 204 | // remove disabled queries 205 | for l := len(d.Collections[i].Items[j].Request.URL.Query) - 1; l >= 0; l-- { 206 | if d.Collections[i].Items[j].Request.URL.Query[l].Disabled { 207 | d.Collections[i].Items[j].Request.URL.Query = append(d.Collections[i].Items[j].Request.URL.Query[:l], d.Collections[i].Items[j].Request.URL.Query[l+1:]...) 208 | } 209 | } 210 | // remove disabled form-data 211 | for m := len(d.Collections[i].Items[j].Request.Body.FormData) - 1; m >= 0; m-- { 212 | if d.Collections[i].Items[j].Request.Body.FormData[m].Disabled { 213 | d.Collections[i].Items[j].Request.Body.FormData = append(d.Collections[i].Items[j].Request.Body.FormData[:m], d.Collections[i].Items[j].Request.Body.FormData[m+1:]...) 214 | } 215 | } 216 | // remove disabled urlencoded 217 | for n := len(d.Collections[i].Items[j].Request.Body.URLEncoded) - 1; n >= 0; n-- { 218 | if d.Collections[i].Items[j].Request.Body.URLEncoded[n].Disabled { 219 | d.Collections[i].Items[j].Request.Body.URLEncoded = append(d.Collections[i].Items[j].Request.Body.URLEncoded[:n], d.Collections[i].Items[j].Request.Body.URLEncoded[n+1:]...) 220 | } 221 | } 222 | } 223 | } 224 | return 225 | } 226 | 227 | //removeItemResponseRequestDisabledField removes disabled field from response originalRequest 228 | func (d *Documentation) removeItemResponseRequestDisabledField() { 229 | for i, c := range d.Collections { 230 | for j, item := range c.Items { 231 | for o, _ := range item.Responses { 232 | // remove disabled headers 233 | for k := len(d.Collections[i].Items[j].Responses[o].OriginalRequest.Headers) - 1; k >= 0; k-- { 234 | if d.Collections[i].Items[j].Responses[o].OriginalRequest.Headers[k].Disabled { 235 | d.Collections[i].Items[j].Responses[o].OriginalRequest.Headers = append(d.Collections[i].Items[j].Responses[o].OriginalRequest.Headers[:k], d.Collections[i].Items[j].Responses[o].OriginalRequest.Headers[k+1:]...) 236 | } 237 | } 238 | // remove disabled queries 239 | for l := len(d.Collections[i].Items[j].Responses[o].OriginalRequest.URL.Query) - 1; l >= 0; l-- { 240 | if d.Collections[i].Items[j].Responses[o].OriginalRequest.URL.Query[l].Disabled { 241 | d.Collections[i].Items[j].Responses[o].OriginalRequest.URL.Query = append(d.Collections[i].Items[j].Responses[o].OriginalRequest.URL.Query[:l], d.Collections[i].Items[j].Responses[o].OriginalRequest.URL.Query[l+1:]...) 242 | } 243 | } 244 | // remove disabled form-data 245 | for m := len(d.Collections[i].Items[j].Responses[o].OriginalRequest.Body.FormData) - 1; m >= 0; m-- { 246 | if d.Collections[i].Items[j].Responses[o].OriginalRequest.Body.FormData[m].Disabled { 247 | d.Collections[i].Items[j].Responses[o].OriginalRequest.Body.FormData = append(d.Collections[i].Items[j].Responses[o].OriginalRequest.Body.FormData[:m], d.Collections[i].Items[j].Responses[o].OriginalRequest.Body.FormData[m+1:]...) 248 | } 249 | } 250 | // remove disabled urlencoded 251 | for n := len(d.Collections[i].Items[j].Responses[o].OriginalRequest.Body.URLEncoded) - 1; n >= 0; n-- { 252 | if d.Collections[i].Items[j].Responses[o].OriginalRequest.Body.URLEncoded[n].Disabled { 253 | d.Collections[i].Items[j].Responses[o].OriginalRequest.Body.URLEncoded = append(d.Collections[i].Items[j].Responses[o].OriginalRequest.Body.URLEncoded[:n], d.Collections[i].Items[j].Responses[o].OriginalRequest.Body.URLEncoded[n+1:]...) 254 | } 255 | } 256 | } 257 | } 258 | } 259 | return 260 | } 261 | -------------------------------------------------------------------------------- /collection/collection_test.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "testing" 7 | ) 8 | 9 | func TestDocumentation_Open(t *testing.T) { 10 | file, err := os.Open("collection.json") 11 | if err != nil { 12 | t.Error(err) 13 | } 14 | d := &Documentation{} 15 | err = d.Open(file) 16 | if err != nil { 17 | t.Error(err) 18 | } 19 | 20 | for _, c := range d.Collections { 21 | for _, i := range c.Items { 22 | for _, r := range i.Responses { 23 | fmt.Println("x", r) 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /collection/env.go: -------------------------------------------------------------------------------- 1 | package collection 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | ) 7 | 8 | //Environment represents environment variables 9 | type Environment struct { 10 | ID json.RawMessage `json:"id"` 11 | Name string `json:"name"` 12 | Values []Field `json:"values"` 13 | PostmanVariableScope json.RawMessage `json:"_postman_variable_scope"` 14 | PostmanExportedAt json.RawMessage `json:"_postman_exported_at"` 15 | PostmanExportedUsing json.RawMessage `json:"_postman_exported_using"` 16 | } 17 | 18 | // Open open an environment file 19 | func (e *Environment) Open(rdr io.Reader) error { 20 | dcr := json.NewDecoder(rdr) 21 | if err := dcr.Decode(&e); err != nil { 22 | return err 23 | } 24 | for i := len(e.Values) - 1; i >= 0; i-- { 25 | if !e.Values[i].Enabled { 26 | e.Values = append(e.Values[:i], e.Values[i+1:]...) 27 | } 28 | } 29 | return nil 30 | } 31 | 32 | func (e *Environment) upsertVariable(f Field) { 33 | var update bool 34 | for i, v := range e.Values { 35 | if v.Key == f.Key { 36 | e.Values[i] = f 37 | update = true 38 | } 39 | } 40 | if !update { 41 | e.Values = append(e.Values, f) 42 | } 43 | } 44 | 45 | // SetCollectionVariables set collection variables to the env // if env provided then the values will be replaced 46 | func (e *Environment) SetCollectionVariables(vv []Field) { 47 | for _, v := range vv { 48 | if !v.Disabled { 49 | e.upsertVariable(v) 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /generate-asset.go: -------------------------------------------------------------------------------- 1 | // +build generateasset 2 | 3 | package main 4 | 5 | import ( 6 | "log" 7 | "net/http" 8 | 9 | "github.com/shurcooL/vfsgen" 10 | ) 11 | 12 | func main() { 13 | var fs http.FileSystem = http.Dir("./assets/") 14 | err := vfsgen.Generate(fs, vfsgen.Options{ 15 | Filename: "./assets_bin/assets.go", 16 | PackageName: "assets_bin", 17 | VariableName: "AssetFS", 18 | }) 19 | if err != nil { 20 | log.Fatalln(err) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/thedevsaddam/docgen 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/Masterminds/semver v1.5.0 7 | github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 8 | github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect 9 | github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 // indirect 10 | github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd 11 | github.com/spf13/cobra v1.3.0 12 | github.com/tdewolff/minify v2.3.4+incompatible 13 | github.com/tdewolff/parse v2.3.2+incompatible // indirect 14 | github.com/tdewolff/test v1.0.3 // indirect 15 | gopkg.in/russross/blackfriday.v2 v2.0.0 16 | ) 17 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | exec_curl(){ 4 | echo "Found docgen latest version: $VERSION" 5 | echo "Download may take few minutes depending on your internet speed" 6 | echo "Downloading docgen to $2" 7 | curl -L --silent --connect-timeout 30 --retry-delay 5 --retry 5 -o "$2" "$1" 8 | } 9 | 10 | OS=`uname` 11 | ARCH=`uname -m` 12 | VERSION=$1 13 | URL=https://github.com/thedevsaddam/docgen 14 | TARGET=/usr/local/bin/docgen 15 | MESSAGE_START="Installing docgen" 16 | MESSAGE_END="Installation complete" 17 | 18 | if [ "$VERSION" == "" ]; then 19 | LATEST_RELEASE=$(curl -L -s -H 'Accept: application/json' $URL/releases/latest) 20 | VERSION=$(echo $LATEST_RELEASE | sed -e 's/.*"tag_name":"\([^"]*\)".*/\1/') 21 | fi 22 | 23 | if [ "$OS" == "Darwin" ]; then 24 | if [ "$ARCH" == "x86_64" ]; then 25 | exec_curl $URL/releases/download/$VERSION/darwin_amd64 $TARGET 26 | echo "$MESSAGE_START" 27 | chmod +x $TARGET 28 | echo "$MESSAGE_END" 29 | docgen 30 | fi 31 | 32 | if [ "$ARCH" == "arm64" ] || [ "$ARCH" == "aarch64" ]; then 33 | exec_curl $URL/releases/download/$VERSION/darwin_arm64 $TARGET 34 | echo "$MESSAGE_START" 35 | chmod +x $TARGET 36 | echo "$MESSAGE_END" 37 | docgen 38 | fi 39 | 40 | elif [ "$OS" == "Linux" ]; then 41 | if [ "$ARCH" == "x86_64" ]; then 42 | exec_curl $URL/releases/download/$VERSION/linux_amd64 $TARGET 43 | echo "$MESSAGE_START" 44 | chmod +x $TARGET 45 | echo "$MESSAGE_END" 46 | docgen 47 | fi 48 | 49 | if [ "$ARCH" == "arm64" ] || [ "$ARCH" == "aarch64" ]; then 50 | exec_curl $URL/releases/download/$VERSION/linux_arm64 $TARGET 51 | echo "$MESSAGE_START" 52 | chmod +x $TARGET 53 | echo "$MESSAGE_END" 54 | docgen 55 | fi 56 | 57 | if [ "$ARCH" == "i368" ]; then 58 | exec_curl $URL/releases/download/$VERSION/linux_386 $TARGET 59 | chmod +x $TARGET 60 | docgen 61 | fi 62 | fi -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | //go:generate go run generate-asset.go 2 | 3 | package main 4 | 5 | import ( 6 | "github.com/thedevsaddam/docgen/cmd" 7 | ) 8 | 9 | func main() { 10 | cmd.Execute() 11 | } 12 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thedevsaddam/docgen/67e675d7b791153303566ad3ea18a01d3be22125/screenshot.png -------------------------------------------------------------------------------- /uninstall.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | TARGET=/usr/local/bin/docgen 4 | MESSAGE_START="Removing docgen" 5 | MESSAGE_END="Docgen removed" 6 | 7 | echo "$MESSAGE_START" 8 | rm $TARGET 9 | echo "$MESSAGE_END" 10 | 11 | -------------------------------------------------------------------------------- /update/github.go: -------------------------------------------------------------------------------- 1 | package update 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | "io" 8 | "net/http" 9 | "os" 10 | "path/filepath" 11 | "time" 12 | 13 | "github.com/kardianos/osext" 14 | ) 15 | 16 | type ( 17 | // ReleaseInfo represents github latest release info 18 | ReleaseInfo struct { 19 | TagName string `json:"tag_name"` 20 | Name string `json:"name"` 21 | Body string `json:"body"` 22 | Draft bool `json:"draft"` 23 | Prerelease bool `json:"prerelease"` 24 | CreatedAt time.Time `json:"created_at"` 25 | PublishedAt time.Time `json:"published_at"` 26 | Assets []Asset `json:"assets"` 27 | } 28 | Asset struct { 29 | Name string `json:"name"` 30 | Size int `json:"size"` 31 | DownloadCount int `json:"download_count"` 32 | BrowserDownloadURL string `json:"browser_download_url"` 33 | } 34 | ) 35 | 36 | func (r *ReleaseInfo) getDownloadURL(name string) string { 37 | for _, a := range r.Assets { 38 | if a.Name == name { 39 | return a.BrowserDownloadURL 40 | } 41 | } 42 | return "" 43 | } 44 | 45 | // fetchReleaseInfo return githublatest release info 46 | func fetchReleaseInfo(ctx context.Context) (*ReleaseInfo, error) { 47 | url := "https://api.github.com/repos/thedevsaddam/docgen/releases/latest" 48 | ctx, cancel := context.WithTimeout(ctx, 3*time.Second) 49 | defer cancel() 50 | req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) 51 | if err != nil { 52 | return nil, err 53 | } 54 | resp, err := http.DefaultClient.Do(req) 55 | if err != nil { 56 | return nil, err 57 | } 58 | defer resp.Body.Close() 59 | 60 | if resp.StatusCode != http.StatusOK { 61 | return nil, errors.New("update: failed to fetch latest release") 62 | } 63 | 64 | rf := ReleaseInfo{} 65 | if err := json.NewDecoder(resp.Body).Decode(&rf); err != nil { 66 | return nil, err 67 | } 68 | return &rf, nil 69 | } 70 | 71 | // updateBinary download the binary in current binary and replace the old one 72 | func updateBinary(ctx context.Context, url string) error { 73 | if url == "" { 74 | return errors.New("update: empty download url") 75 | } 76 | ctx, cancel := context.WithTimeout(ctx, 5*time.Minute) 77 | defer cancel() 78 | req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) 79 | if err != nil { 80 | return err 81 | } 82 | resp, err := http.DefaultClient.Do(req) 83 | if err != nil { 84 | return err 85 | } 86 | defer resp.Body.Close() 87 | 88 | if resp.StatusCode != http.StatusOK { 89 | return errors.New("update: failed to fetch binary file") 90 | } 91 | 92 | dir, err := osext.ExecutableFolder() 93 | if err != nil { 94 | return err 95 | } 96 | 97 | fileName := filepath.Join(dir, filepath.Base(os.Args[0])) 98 | tmpFile := fileName + ".tmp" 99 | 100 | if err := os.Chmod(fileName, 0777); err != nil { 101 | return err 102 | } 103 | 104 | if err := os.Rename(fileName, tmpFile); err != nil { 105 | return err 106 | } 107 | 108 | f, err := os.Create(fileName) 109 | if err != nil { 110 | return err 111 | } 112 | 113 | if err := os.Chmod(fileName, 0777); err != nil { 114 | return err 115 | } 116 | 117 | _, err = io.Copy(f, resp.Body) 118 | if err != nil { 119 | if err := os.Rename(tmpFile, fileName); err != nil { 120 | return err 121 | } 122 | return err 123 | } 124 | 125 | return os.Remove(tmpFile) 126 | } 127 | -------------------------------------------------------------------------------- /update/update.go: -------------------------------------------------------------------------------- 1 | package update 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "runtime" 8 | "time" 9 | 10 | "github.com/Masterminds/semver" 11 | ) 12 | 13 | const ( 14 | layoutISO = "2006-01-02" 15 | updateInDays = 7 16 | ) 17 | 18 | // SelfUpdate update the application to its latest version 19 | // if the current release is 3days old and has a new update 20 | func SelfUpdate(ctx context.Context, buildDate, version string) error { 21 | if buildDate == "unknown" { 22 | return errors.New("update: unable to update based on unkown build date/version") 23 | } 24 | currBinaryReleaseDate, err := time.Parse(layoutISO, buildDate) 25 | if err != nil { 26 | return fmt.Errorf("update: %v", err) 27 | } 28 | if (time.Since(currBinaryReleaseDate).Hours() / 24) <= updateInDays { 29 | return nil 30 | } 31 | 32 | releaseInfo, err := fetchReleaseInfo(ctx) 33 | if err != nil { 34 | return fmt.Errorf("update: %v", err) 35 | } 36 | 37 | if releaseInfo.Draft || releaseInfo.Prerelease { 38 | return nil 39 | } 40 | 41 | c, err := semver.NewConstraint(">" + version) 42 | if err != nil { 43 | return fmt.Errorf("update: %v", err) 44 | } 45 | 46 | v, err := semver.NewVersion(releaseInfo.TagName) 47 | if err != nil { 48 | return fmt.Errorf("update: %v", err) 49 | } 50 | // Check if the version meets the constraints. The a variable will be true. 51 | if !c.Check(v) { 52 | return nil 53 | } 54 | 55 | os := runtime.GOOS 56 | arch := runtime.GOARCH 57 | 58 | fmt.Println("Found newer version:", releaseInfo.TagName) 59 | fmt.Printf("Updating from %s to %s\n", version, releaseInfo.Name) 60 | 61 | name := fmt.Sprintf("%s_%s", os, arch) 62 | if os == "windows" { 63 | name = name + ".exe" 64 | } 65 | 66 | err = updateBinary(ctx, releaseInfo.getDownloadURL(name)) 67 | if err != nil { 68 | return err 69 | } 70 | 71 | fmt.Println() 72 | fmt.Println("Update includes:") 73 | fmt.Print(releaseInfo.Body) 74 | fmt.Println() 75 | 76 | return err 77 | } 78 | --------------------------------------------------------------------------------