├── .github ├── FUNDING.yml ├── releaser.Dockerfile └── workflows │ ├── go.yml │ └── release.yml ├── .gitignore ├── .golangci.yml ├── .goreleaser.yaml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── cmd └── isite │ └── main.go ├── go.mod ├── go.sum └── pkg ├── cli ├── cli.go ├── generate.go └── version.go ├── site.go ├── ssg ├── ssg.go └── zola.go ├── types └── types.go ├── utils ├── repo.go └── toml.go └── version └── version.go /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [kemingy] 2 | -------------------------------------------------------------------------------- /.github/releaser.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | 3 | COPY isite /usr/bin/isite 4 | 5 | ENTRYPOINT [ "/usr/bin/isite" ] 6 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 3 | 4 | name: Go 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | paths: 10 | - '.github/workflows/**' 11 | - '**.go' 12 | - 'Makefile' 13 | - 'go.**' 14 | pull_request: 15 | branches: [ "main" ] 16 | paths: 17 | - '.github/workflows/**' 18 | - '**.go' 19 | - 'Makefile' 20 | - 'go.**' 21 | merge_group: 22 | workflow_dispatch: 23 | 24 | jobs: 25 | 26 | build: 27 | runs-on: ubuntu-latest 28 | steps: 29 | - uses: actions/checkout@v4 30 | 31 | - name: Set up Go 32 | uses: actions/setup-go@v5 33 | 34 | - name: Build 35 | run: make build 36 | 37 | - name: Lint 38 | run: make lint 39 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: goreleaser 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | workflow_dispatch: 8 | 9 | # to publish ghcr.io 10 | permissions: 11 | contents: write 12 | packages: write 13 | 14 | jobs: 15 | goreleaser: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 21 | - uses: actions/setup-go@v5 22 | 23 | - name: Login to ghcr.io 24 | uses: docker/login-action@v3 25 | with: 26 | registry: ghcr.io 27 | username: ${{ github.actor }} 28 | password: ${{ secrets.GITHUB_TOKEN }} 29 | 30 | - uses: goreleaser/goreleaser-action@v6 31 | with: 32 | distribution: goreleaser 33 | version: "~> v2" 34 | args: release --clean 35 | env: 36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | 23 | bin 24 | output 25 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | linters: 3 | default: none 4 | enable: 5 | - bodyclose 6 | - copyloopvar 7 | - dogsled 8 | - dupl 9 | - errcheck 10 | - errorlint 11 | - funlen 12 | - goconst 13 | - govet 14 | - ineffassign 15 | - misspell 16 | - revive 17 | - staticcheck 18 | - unconvert 19 | - unparam 20 | - unused 21 | - whitespace 22 | exclusions: 23 | generated: lax 24 | presets: 25 | - comments 26 | - common-false-positives 27 | - legacy 28 | - std-error-handling 29 | paths: 30 | - third_party$ 31 | - builtin$ 32 | - examples$ 33 | formatters: 34 | enable: 35 | - gofmt 36 | - goimports 37 | exclusions: 38 | generated: lax 39 | paths: 40 | - third_party$ 41 | - builtin$ 42 | - examples$ 43 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | project_name: isite 3 | builds: 4 | - env: 5 | - CGO_ENABLED=0 6 | id: isite 7 | main: cmd/isite/main.go 8 | goos: 9 | - linux 10 | - darwin 11 | - windows 12 | goarch: 13 | - amd64 14 | - arm64 15 | goarm: 16 | - "7" 17 | flags: 18 | - -trimpath 19 | ldflags: 20 | - -s -w 21 | - -X github.com/kemingy/isite/pkg/version.gitTag={{ .Tag }} 22 | - -X github.com/kemingy/isite/pkg/version.buildDate={{ .Date }} 23 | - -X github.com/kemingy/isite/pkg/version.gitCommit={{ .Commit }} 24 | 25 | archives: 26 | - name_template: >- 27 | {{- .ProjectName }}_ 28 | {{- .Version }}_ 29 | {{- title .Os }}_ 30 | {{- if eq .Arch "amd64" }}x86_64 31 | {{- else if eq .Arch "386" }}i386 32 | {{- else }}{{ .Arch }}{{ end }} 33 | {{- if .Arm }}v{{ .Arm }}{{ end -}} 34 | format_overrides: 35 | - goos: windows 36 | format: zip 37 | builds_info: 38 | group: root 39 | owner: root 40 | files: 41 | - README.md 42 | - LICENSE 43 | 44 | changelog: 45 | use: github 46 | sort: asc 47 | groups: 48 | - title: 'Exciting New Features 🎉' 49 | regexp: "^.*feat.*" 50 | order: 0 51 | - title: 'Bug Fix 🛠' 52 | regexp: "^.*(Fix|fix|bug).*" 53 | order: 1 54 | - title: 'Refactor 🏗️' 55 | regexp: "^.*refact.*" 56 | order: 2 57 | - title: 'Documentation 🖊️' 58 | regexp: "^.*docs.*" 59 | order: 3 60 | - title: 'Others:' 61 | order: 999 62 | 63 | dockers: 64 | - image_templates: 65 | - "ghcr.io/kemingy/isite:{{ .Version }}-amd64" 66 | use: buildx 67 | dockerfile: .github/releaser.Dockerfile 68 | ids: 69 | - isite 70 | build_flag_templates: 71 | - "--platform=linux/amd64" 72 | - image_templates: 73 | - "ghcr.io/kemingy/isite:{{ .Version }}-arm64" 74 | use: buildx 75 | dockerfile: .github/releaser.Dockerfile 76 | ids: 77 | - isite 78 | build_flag_templates: 79 | - "--platform=linux/arm64/v8" 80 | goarch: arm64 81 | 82 | docker_manifests: 83 | - name_template: "ghcr.io/kemingy/isite:{{ .Version }}" 84 | image_templates: 85 | - "ghcr.io/kemingy/isite:{{ .Version }}-amd64" 86 | - "ghcr.io/kemingy/isite:{{ .Version }}-arm64" 87 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.23-bookworm as builder 2 | 3 | WORKDIR /workspace 4 | 5 | COPY go.mod go.sum ./ 6 | RUN go mod download && go mod tidy 7 | 8 | COPY . . 9 | RUN make build 10 | 11 | FROM ubuntu:24.04 as runner 12 | 13 | COPY --from=builder /workspace/bin/isite /usr/local/bin/isite 14 | 15 | ENTRYPOINT ["/usr/local/bin/isite"] 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2023 Keming 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TARGET := isite 2 | OUTPUT_DIR := ./bin 3 | CMD_DIR := ./cmd 4 | 5 | # Golang dir 6 | ROOT := github.com/kemingy/isite 7 | GOPATH ?= $(shell go env GOPATH) 8 | BIN_DIR := $(GOPATH)/bin 9 | GOLANGCI_LINT := $(BIN_DIR)/golangci-lint 10 | 11 | # Version 12 | GIT_TAG ?= $(shell git describe --tags --dirty=.dirty --always) 13 | BUILD_DATE=$(shell date -u +'%Y-%m-%dT%H:%M:%SZ') 14 | GIT_COMMIT=$(shell git rev-parse HEAD) 15 | 16 | BUILD_FLAGS ?= -s -w \ 17 | -X $(ROOT)/pkg/version.gitTag=$(GIT_TAG) \ 18 | -X $(ROOT)/pkg/version.buildDate=$(BUILD_DATE) \ 19 | -X $(ROOT)/pkg/version.gitCommit=$(GIT_COMMIT) \ 20 | 21 | .DEFAULT_GOAL:=build 22 | 23 | build: 24 | @go build -trimpath -o $(OUTPUT_DIR)/$(TARGET) -ldflags "$(BUILD_FLAGS)" $(CMD_DIR)/$(TARGET) 25 | 26 | format: 27 | @go fmt ./... 28 | 29 | lint: $(GOLANGCI_LINT) 30 | @$(GOLANGCI_LINT) run 31 | 32 | $(GOLANGCI_LINT): 33 | @curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(BIN_DIR) 34 | 35 | clean: 36 | @-rm -rf $(OUTPUT_DIR) 37 | @-rm -rf output 38 | 39 | update: 40 | @go get -u ./... 41 | @go mod tidy 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # isite 2 | 3 | [![Go](https://github.com/kemingy/isite/actions/workflows/go.yml/badge.svg)](https://github.com/kemingy/isite/actions/workflows/go.yml) 4 | 5 | Convert GitHub **i**ssues to a web**site**. 6 | 7 | ## Examples 8 | 9 | - https://github.com/kemingy/withcode ➡️ https://kemingy.github.io/withcode/ 10 | - https://github.com/yihong0618/gitblog ➡️ https://blog.yihong0618.me/ 11 | 12 | ## Usage 13 | 14 | ```bash 15 | isite generate --help 16 | ``` 17 | 18 | After generating the Markdown based documents, you can build the website with the following engines. 19 | 20 | ## Engines 21 | 22 | - [x] [zola](https://github.com/getzola/zola) 23 | - default theme: [Even](https://github.com/kemingy/even), modified to support comments and reactions 24 | - [ ] [hugo](https://github.com/gohugoio/hugo) 25 | 26 | ## Installation 27 | 28 | - GitHub Releases: download the pre-built binaries from the [releases](https://github.com/kemingy/isite/releases) page. 29 | - Docker Image: [`docker pull ghcr.io/kemingy/isite`](https://github.com/kemingy/isite/pkgs/container/isite) 30 | 31 | ## GitHub Actions 32 | 33 | > [!IMPORTANT] 34 | > Please remember to enable the GitHub Pages with GitHub Actions as the source. 35 | 36 | You can audit and apply the following GitHub Actions workflow to deploy the static content generated by `isite` to GitHub Pages. 37 | 38 | ```yaml 39 | name: Deploy static content to Pages 40 | 41 | on: 42 | issues: 43 | types: 44 | - opened 45 | - edited 46 | - closed 47 | - reopened 48 | - labeled 49 | - unlabeled 50 | workflow_dispatch: 51 | 52 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 53 | permissions: 54 | contents: read 55 | pages: write 56 | id-token: write 57 | 58 | concurrency: 59 | group: ${{ github.workflow }} 60 | cancel-in-progress: true 61 | 62 | jobs: 63 | deploy: 64 | environment: 65 | name: github-pages 66 | url: ${{ steps.deployment.outputs.page_url }} 67 | runs-on: ubuntu-latest 68 | env: 69 | GH_TOKEN: ${{ github.token }} 70 | # bump the versions here 71 | ISITE_VERSION: v0.2.2 72 | ZOLA_VERSION: v0.20.0 73 | USER: ${{ github.repository_owner }} 74 | REPO: ${{ github.event.repository.name }} 75 | # change this to your custom domain name 76 | BASE_URL: https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }} 77 | steps: 78 | - name: Checkout 79 | uses: actions/checkout@v4 80 | - name: Generate markdown 81 | run: | 82 | gh release download $ISITE_VERSION --repo kemingy/isite -p '*Linux_x86_64*' -O- | tar -xz -C /tmp && mv /tmp/isite /usr/local/bin 83 | isite generate --user $USER --repo $REPO 84 | gh release download $ZOLA_VERSION --repo getzola/zola -p '*x86_64-unknown-linux*' -O- | tar -xz -C /tmp && mv /tmp/zola /usr/local/bin 85 | cd output && zola build --base-url $BASE_URL 86 | - name: Setup Pages 87 | uses: actions/configure-pages@v5 88 | - name: Upload artifact 89 | uses: actions/upload-pages-artifact@v3 90 | with: 91 | path: 'output/public' 92 | - name: Deploy to GitHub Pages 93 | id: deployment 94 | uses: actions/deploy-pages@v4 95 | ``` 96 | 97 | ## Customization 98 | 99 | ### Domain name 100 | 101 | Change the `BASE_URL` in the GitHub Actions workflow to your custom domain name. 102 | 103 | ### Other themes 104 | 105 | ```bash 106 | isite generate --theme --theme-repo 107 | ``` 108 | 109 | ### Backup Markdown files to the Repo 110 | 111 | ```yaml 112 | - name: backup markdown files 113 | env: 114 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 115 | run: | 116 | git config user.name "GitHub Actions Bot" 117 | git config user.email "github-actions@users.noreply.github.com" 118 | git checkout -b backup 119 | git add output/content 120 | git commit -m "Backup markdown files" 121 | git push --force origin backup 122 | ``` 123 | -------------------------------------------------------------------------------- /cmd/isite/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/kemingy/isite/pkg/cli" 5 | ) 6 | 7 | func main() { 8 | cli.Execute() 9 | } 10 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kemingy/isite 2 | 3 | go 1.23.5 4 | 5 | require ( 6 | github.com/cli/go-gh/v2 v2.12.1 7 | github.com/cockroachdb/errors v1.12.0 8 | github.com/go-git/go-git/v5 v5.16.0 9 | github.com/spf13/cobra v1.9.1 10 | ) 11 | 12 | require ( 13 | dario.cat/mergo v1.0.2 // indirect 14 | github.com/Microsoft/go-winio v0.6.2 // indirect 15 | github.com/ProtonMail/go-crypto v1.3.0 // indirect 16 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 17 | github.com/cli/safeexec v1.0.1 // indirect 18 | github.com/cli/shurcooL-graphql v0.0.4 // indirect 19 | github.com/cloudflare/circl v1.6.1 // indirect 20 | github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506 // indirect 21 | github.com/cockroachdb/redact v1.1.6 // indirect 22 | github.com/cyphar/filepath-securejoin v0.4.1 // indirect 23 | github.com/emirpasic/gods v1.18.1 // indirect 24 | github.com/getsentry/sentry-go v0.33.0 // indirect 25 | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect 26 | github.com/go-git/go-billy/v5 v5.6.2 // indirect 27 | github.com/gogo/protobuf v1.3.2 // indirect 28 | github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect 29 | github.com/henvic/httpretty v0.1.4 // indirect 30 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 31 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect 32 | github.com/kevinburke/ssh_config v1.2.0 // indirect 33 | github.com/kr/pretty v0.3.1 // indirect 34 | github.com/kr/text v0.2.0 // indirect 35 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 36 | github.com/mattn/go-isatty v0.0.20 // indirect 37 | github.com/muesli/termenv v0.16.0 // indirect 38 | github.com/pjbgf/sha1cd v0.3.2 // indirect 39 | github.com/pkg/errors v0.9.1 // indirect 40 | github.com/rivo/uniseg v0.4.7 // indirect 41 | github.com/rogpeppe/go-internal v1.14.1 // indirect 42 | github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect 43 | github.com/skeema/knownhosts v1.3.1 // indirect 44 | github.com/spf13/pflag v1.0.6 // indirect 45 | github.com/thlib/go-timezone-local v0.0.6 // indirect 46 | github.com/xanzy/ssh-agent v0.3.3 // indirect 47 | golang.org/x/crypto v0.38.0 // indirect 48 | golang.org/x/net v0.40.0 // indirect 49 | golang.org/x/sys v0.33.0 // indirect 50 | golang.org/x/term v0.32.0 // indirect 51 | golang.org/x/text v0.25.0 // indirect 52 | golang.org/x/tools v0.32.0 // indirect 53 | gopkg.in/warnings.v0 v0.1.2 // indirect 54 | gopkg.in/yaml.v3 v3.0.1 // indirect 55 | ) 56 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= 2 | dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= 3 | github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= 4 | github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= 5 | github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 6 | github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= 7 | github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= 8 | github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= 9 | github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= 10 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= 11 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= 12 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= 13 | github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 14 | github.com/cli/go-gh/v2 v2.12.1 h1:SVt1/afj5FRAythyMV3WJKaUfDNsxXTIe7arZbwTWKA= 15 | github.com/cli/go-gh/v2 v2.12.1/go.mod h1:+5aXmEOJsH9fc9mBHfincDwnS02j2AIA/DsTH0Bk5uw= 16 | github.com/cli/safeexec v1.0.1 h1:e/C79PbXF4yYTN/wauC4tviMxEV13BwljGj0N9j+N00= 17 | github.com/cli/safeexec v1.0.1/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q= 18 | github.com/cli/shurcooL-graphql v0.0.4 h1:6MogPnQJLjKkaXPyGqPRXOI2qCsQdqNfUY1QSJu2GuY= 19 | github.com/cli/shurcooL-graphql v0.0.4/go.mod h1:3waN4u02FiZivIV+p1y4d0Jo1jc6BViMA73C+sZo2fk= 20 | github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= 21 | github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= 22 | github.com/cockroachdb/errors v1.12.0 h1:d7oCs6vuIMUQRVbi6jWWWEJZahLCfJpnJSVobd1/sUo= 23 | github.com/cockroachdb/errors v1.12.0/go.mod h1:SvzfYNNBshAVbZ8wzNc/UPK3w1vf0dKDUP41ucAIf7g= 24 | github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506 h1:ASDL+UJcILMqgNeV5jiqR4j+sTuvQNHdf2chuKj1M5k= 25 | github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506/go.mod h1:Mw7HqKr2kdtu6aYGn3tPmAftiP3QPX63LdK/zcariIo= 26 | github.com/cockroachdb/redact v1.1.6 h1:zXJBwDZ84xJNlHl1rMyCojqyIxv+7YUpQiJLQ7n4314= 27 | github.com/cockroachdb/redact v1.1.6/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= 28 | github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 29 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 30 | github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= 31 | github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= 32 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 33 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 34 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 35 | github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= 36 | github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= 37 | github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= 38 | github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= 39 | github.com/getsentry/sentry-go v0.33.0 h1:YWyDii0KGVov3xOaamOnF0mjOrqSjBqwv48UEzn7QFg= 40 | github.com/getsentry/sentry-go v0.33.0/go.mod h1:C55omcY9ChRQIUcVcGcs+Zdy4ZpQGvNJ7JYHIoSWOtE= 41 | github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= 42 | github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= 43 | github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= 44 | github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= 45 | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= 46 | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= 47 | github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= 48 | github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= 49 | github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= 50 | github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= 51 | github.com/go-git/go-git/v5 v5.16.0 h1:k3kuOEpkc0DeY7xlL6NaaNg39xdgQbtH5mwCafHO9AQ= 52 | github.com/go-git/go-git/v5 v5.16.0/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= 53 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 54 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 55 | github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= 56 | github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= 57 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 58 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 59 | github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= 60 | github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= 61 | github.com/henvic/httpretty v0.1.4 h1:Jo7uwIRWVFxkqOnErcoYfH90o3ddQyVrSANeS4cxYmU= 62 | github.com/henvic/httpretty v0.1.4/go.mod h1:Dn60sQTZfbt2dYsdUSNsCljyF4AfdqnuJFDLJA1I4AM= 63 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 64 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 65 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= 66 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= 67 | github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= 68 | github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= 69 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 70 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 71 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 72 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 73 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 74 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 75 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 76 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 77 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 78 | github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= 79 | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 80 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 81 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 82 | github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= 83 | github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= 84 | github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= 85 | github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= 86 | github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= 87 | github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= 88 | github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= 89 | github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= 90 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 91 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 92 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 93 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 94 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 95 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 96 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 97 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 98 | github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= 99 | github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= 100 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 101 | github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= 102 | github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= 103 | github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 104 | github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= 105 | github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= 106 | github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= 107 | github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= 108 | github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= 109 | github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 110 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 111 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 112 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 113 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 114 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 115 | github.com/thlib/go-timezone-local v0.0.6 h1:Ii3QJ4FhosL/+eCZl6Hsdr4DDU4tfevNoV83yAEo2tU= 116 | github.com/thlib/go-timezone-local v0.0.6/go.mod h1:/Tnicc6m/lsJE0irFMA0LfIwTBo4QP7A8IfyIv4zZKI= 117 | github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= 118 | github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= 119 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 120 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 121 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 122 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 123 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 124 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 125 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 126 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 127 | golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= 128 | golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= 129 | golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= 130 | golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= 131 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 132 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 133 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 134 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 135 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 136 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 137 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 138 | golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= 139 | golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= 140 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 141 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 142 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 143 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 144 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 145 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 146 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 147 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 148 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 149 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 150 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 151 | golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 152 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 153 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 154 | golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= 155 | golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 156 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 157 | golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= 158 | golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= 159 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 160 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 161 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 162 | golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= 163 | golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= 164 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 165 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 166 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 167 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 168 | golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= 169 | golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= 170 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 171 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 172 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 173 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 174 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 175 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 176 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 177 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 178 | gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY= 179 | gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0= 180 | gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= 181 | gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= 182 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 183 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 184 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 185 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 186 | -------------------------------------------------------------------------------- /pkg/cli/cli.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | var ( 10 | user string 11 | repo string 12 | ) 13 | 14 | var rootCmd = &cobra.Command{ 15 | Use: "isite", 16 | Short: "isite is a tool to generate static site from github issues", 17 | } 18 | 19 | func Execute() { 20 | if err := rootCmd.Execute(); err != nil { 21 | log.Fatal(err) 22 | } 23 | } 24 | 25 | func init() { 26 | rootCmd.PersistentFlags().StringVar(&user, "user", "kemingy", "github user name or organization name") 27 | rootCmd.PersistentFlags().StringVar(&repo, "repo", "isite", "github repository name") 28 | } 29 | -------------------------------------------------------------------------------- /pkg/cli/generate.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/cockroachdb/errors" 7 | "github.com/spf13/cobra" 8 | 9 | "github.com/kemingy/isite/pkg" 10 | ) 11 | 12 | var ( 13 | // filter 14 | creator string 15 | state string 16 | label []string 17 | // config 18 | engine string 19 | output string 20 | title string 21 | theme string 22 | themeRepo string 23 | baseURL string 24 | feed bool 25 | ) 26 | 27 | var generateCmd = &cobra.Command{ 28 | Use: "generate", 29 | Short: "generate static site from github issue", 30 | RunE: generate, 31 | } 32 | 33 | func init() { 34 | rootCmd.AddCommand(generateCmd) 35 | 36 | generateCmd.Flags().StringVar(&creator, "creator", "", "filter the github issue by the creator") 37 | generateCmd.Flags().StringVar(&state, "state", "open", "filter the github issue by the state, default is `open`, choose from [open, closed, all]") 38 | generateCmd.Flags().StringSliceVar(&label, "label", []string{}, "filter the github issue by the labels") 39 | 40 | generateCmd.Flags().StringVar(&engine, "engine", "zola", "the static site generator engine, default is `zola`, choose from [zola]") 41 | generateCmd.Flags().StringVar(&output, "output", "output", "the output dir for the generated files") 42 | generateCmd.Flags().StringVar(&title, "title", "", "the title of the static site, if not set, will use the repository name") 43 | generateCmd.Flags().StringVar(&theme, "theme", "", "the theme name of the static site") 44 | generateCmd.Flags().StringVar(&themeRepo, "theme-repo", "", "the theme repository of the static site, format is `/`") 45 | generateCmd.Flags().StringVar(&baseURL, "base-url", "/", "the base url of the static site") 46 | generateCmd.Flags().BoolVar(&feed, "feed", true, "generate feed or not") 47 | } 48 | 49 | func generate(_ *cobra.Command, _ []string) error { 50 | if (theme == "" && themeRepo != "") || (theme != "" && themeRepo == "") { 51 | return errors.New("`theme` and `theme-repo` should be set together") 52 | } 53 | 54 | website := pkg.NewWebsite( 55 | user, repo, 56 | &pkg.IssueFilterByCreator{Creator: creator}, 57 | &pkg.IssueFilterByState{State: state}, 58 | &pkg.IssueFilterByLabels{Labels: label}, 59 | ) 60 | if err := website.Retrieve(); err != nil { 61 | return err 62 | } 63 | fmt.Printf("found %d issues\n", len(website.Issues)) 64 | return website.Generate(engine, title, theme, themeRepo, baseURL, output, feed) 65 | } 66 | -------------------------------------------------------------------------------- /pkg/cli/version.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | 6 | "github.com/kemingy/isite/pkg/version" 7 | ) 8 | 9 | var ( 10 | short bool 11 | ) 12 | 13 | var versionCmd = &cobra.Command{ 14 | Use: "version", 15 | Short: "Print the version number of isite", 16 | Run: commandVersion, 17 | } 18 | 19 | func init() { 20 | rootCmd.AddCommand(versionCmd) 21 | 22 | versionCmd.Flags().BoolVarP(&short, "short", "s", false, "print the version number only") 23 | } 24 | 25 | func commandVersion(cmd *cobra.Command, _ []string) { 26 | ver := version.GetVersionInfo() 27 | if short { 28 | cmd.Println(ver.Version) 29 | return 30 | } 31 | cmd.Println(ver.PrettyString()) 32 | } 33 | -------------------------------------------------------------------------------- /pkg/site.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "regexp" 8 | "strings" 9 | 10 | "github.com/cli/go-gh/v2/pkg/api" 11 | "github.com/cockroachdb/errors" 12 | 13 | "github.com/kemingy/isite/pkg/ssg" 14 | "github.com/kemingy/isite/pkg/types" 15 | ) 16 | 17 | type issueFilterOption struct { 18 | State string 19 | Creator string 20 | Labels []string 21 | } 22 | 23 | func (o *issueFilterOption) BuildQuery() string { 24 | query := fmt.Sprintf("state=%s", o.State) 25 | if o.Creator == "" { 26 | query += fmt.Sprintf("&creator=%s", o.Creator) 27 | } 28 | if len(o.Labels) > 0 { 29 | query += fmt.Sprintf("&labels=%s", strings.Join(o.Labels, ",")) 30 | } 31 | return query 32 | } 33 | 34 | type Website struct { 35 | User string 36 | Repo string 37 | // options 38 | FilterOption issueFilterOption 39 | PerPage int 40 | // data 41 | Issues []types.Issue 42 | // others 43 | linkRegex *regexp.Regexp 44 | } 45 | 46 | type IssueFilterOption interface { 47 | SetFilterOption(*issueFilterOption) 48 | } 49 | 50 | func NewWebsite(user, repo string, opts ...IssueFilterOption) *Website { 51 | option := issueFilterOption{ 52 | State: "open", 53 | Creator: user, 54 | Labels: []string{}, 55 | } 56 | for _, opt := range opts { 57 | opt.SetFilterOption(&option) 58 | } 59 | linkRegex := regexp.MustCompile(`<([^>]+)>;\s*rel="([^"]+)"`) 60 | return &Website{ 61 | User: user, 62 | Repo: repo, 63 | FilterOption: option, 64 | PerPage: 100, 65 | Issues: []types.Issue{}, 66 | linkRegex: linkRegex, 67 | } 68 | } 69 | 70 | type IssueFilterByCreator struct { 71 | Creator string 72 | } 73 | 74 | func (f *IssueFilterByCreator) SetFilterOption(option *issueFilterOption) { 75 | if f.Creator != "" { 76 | option.Creator = f.Creator 77 | } 78 | } 79 | 80 | type IssueFilterByLabels struct { 81 | Labels []string 82 | } 83 | 84 | func (f *IssueFilterByLabels) SetFilterOption(option *issueFilterOption) { 85 | for _, label := range f.Labels { 86 | if label != "" { 87 | option.Labels = append(option.Labels, label) 88 | } 89 | } 90 | } 91 | 92 | type IssueFilterByState struct { 93 | State string 94 | } 95 | 96 | func (f *IssueFilterByState) SetFilterOption(option *issueFilterOption) { 97 | for _, state := range []string{"open", "closed", "all"} { 98 | if state == f.State { 99 | option.State = f.State 100 | return 101 | } 102 | } 103 | } 104 | 105 | func (w *Website) IssueURL() string { 106 | return fmt.Sprintf( 107 | "repos/%s/%s/issues?%s&per_page=%d", w.User, w.Repo, w.FilterOption.BuildQuery(), w.PerPage) 108 | } 109 | 110 | func (w *Website) CommentURL(issueNumber int) string { 111 | return fmt.Sprintf("repos/%s/%s/issues/%d/comments?per_page=%d", w.User, w.Repo, issueNumber, w.PerPage) 112 | } 113 | 114 | func (w *Website) findNextPage(response *http.Response) (string, bool) { 115 | for _, m := range w.linkRegex.FindAllStringSubmatch(response.Header.Get("Link"), -1) { 116 | if len(m) > 2 && m[2] == "next" { 117 | return m[1], true 118 | } 119 | } 120 | return "", false 121 | } 122 | 123 | func (w *Website) Retrieve() error { 124 | client, err := api.DefaultRESTClient() 125 | if err != nil { 126 | return err 127 | } 128 | 129 | // with pagination: https://github.com/cli/go-gh/blob/d32c104a9a25c9de3d7c7b07a43ae0091441c858/example_gh_test.go#L96 130 | url := w.IssueURL() 131 | var hasNextPage bool 132 | for { 133 | response, err := client.Request(http.MethodGet, url, nil) 134 | if err != nil { 135 | return errors.Wrap(err, "failed to get issues") 136 | } 137 | issues := []types.Issue{} 138 | decoder := json.NewDecoder(response.Body) 139 | err = decoder.Decode(&issues) 140 | if err != nil { 141 | return errors.Wrap(err, "failed to decode issues") 142 | } 143 | // GitHub's REST API considers every pull request an issue, but not every issue is a pull request. 144 | for _, issue := range issues { 145 | // Identify pull requests by the `pull_request` key. 146 | if issue.PullRequest != nil { 147 | continue 148 | } 149 | w.Issues = append(w.Issues, issue) 150 | } 151 | if err := response.Body.Close(); err != nil { 152 | return err 153 | } 154 | if url, hasNextPage = w.findNextPage(response); !hasNextPage { 155 | break 156 | } 157 | } 158 | 159 | // comments 160 | for i, issue := range w.Issues { 161 | url = w.CommentURL(issue.Number) 162 | w.Issues[i].Comments = []types.Comment{} 163 | for { 164 | response, err := client.Request(http.MethodGet, url, nil) 165 | if err != nil { 166 | return errors.Wrapf(err, "failed to get comments for issue #%d", issue.Number) 167 | } 168 | comments := []types.Comment{} 169 | decoder := json.NewDecoder(response.Body) 170 | err = decoder.Decode(&comments) 171 | if err != nil { 172 | return errors.Wrapf(err, "failed to decode comments for issue #%d", issue.Number) 173 | } 174 | w.Issues[i].Comments = append(w.Issues[i].Comments, comments...) 175 | if err := response.Body.Close(); err != nil { 176 | return err 177 | } 178 | if url, hasNextPage = w.findNextPage(response); !hasNextPage { 179 | break 180 | } 181 | } 182 | } 183 | return nil 184 | } 185 | 186 | func (w *Website) Generate(engine, title, theme, themeRepo, baseURL, output string, feed bool) error { 187 | if title == "" { 188 | title = w.Repo 189 | } 190 | generator := ssg.NewGenerator(engine, title, theme, themeRepo, baseURL, feed) 191 | err := generator.Generate(w.Issues, output) 192 | if err != nil { 193 | return errors.Wrap(err, "failed to generate static site") 194 | } 195 | return nil 196 | } 197 | -------------------------------------------------------------------------------- /pkg/ssg/ssg.go: -------------------------------------------------------------------------------- 1 | package ssg 2 | 3 | import ( 4 | "github.com/kemingy/isite/pkg/types" 5 | ) 6 | 7 | type StaticSiteGenerator interface { 8 | Generate(issues []types.Issue, outputDir string) error 9 | } 10 | 11 | func NewGenerator(engine, title, theme, themeRepo, baseURL string, feed bool) StaticSiteGenerator { 12 | switch engine { 13 | case "zola": 14 | return NewZola(title, baseURL, theme, themeRepo, feed) 15 | default: 16 | return nil 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /pkg/ssg/zola.go: -------------------------------------------------------------------------------- 1 | package ssg 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | "text/template" 9 | 10 | "github.com/cockroachdb/errors" 11 | 12 | "github.com/kemingy/isite/pkg/types" 13 | "github.com/kemingy/isite/pkg/utils" 14 | ) 15 | 16 | const ( 17 | zolaDefaultTheme = "even" 18 | zolaDefaultThemeRepo = "kemingy/even" 19 | ) 20 | 21 | const zolaPostTemplate = ` 22 | +++ 23 | title = "{{ .Title }}" 24 | date = "{{ .CreatedAt }}" 25 | authors = ["{{ .User.Login }}"] 26 | [taxonomies] 27 | tags = [{{ range .Labels }} "{{ .Name }}", {{ end }}] 28 | [extra] 29 | author = "{{ .User.Login }}" 30 | avatar = "{{ .User.AvatarURL }}" 31 | issue_url = "{{ .URL }}" 32 | [extra.reactions] 33 | thumbs_up = {{ .Reactions.ThumbUp }} 34 | thumbs_down = {{ .Reactions.ThumbDown }} 35 | laugh = {{ .Reactions.Laugh }} 36 | heart = {{ .Reactions.Heart }} 37 | hooray = {{ .Reactions.Hooray }} 38 | confused = {{ .Reactions.Confused }} 39 | rocket = {{ .Reactions.Rocket }} 40 | eyes = {{ .Reactions.Eyes }} 41 | {{ range .Comments }} 42 | [[extra.comments]] 43 | url = "{{ .HTMLURL }}" 44 | author_name = "{{ .User.Login }}" 45 | author_avatar = "{{ .User.AvatarURL }}" 46 | content = {{ toml_escape .Body }} 47 | updated_at = "{{ .UpdatedAt }}" 48 | {{ end }} 49 | +++ 50 | 51 | {{ .Body }} 52 | ` 53 | 54 | const zolaIndexTemplate = ` 55 | +++ 56 | paginate_by = 10 57 | sort_by = "date" 58 | +++ 59 | ` 60 | 61 | const zolaConfigTemplate = ` 62 | title = "{{ .Title }}" 63 | base_url = "{{ .BaseURL }}" 64 | theme = "{{ .ThemeName }}" 65 | compile_sass = true 66 | generate_feeds = {{ .Feed }} 67 | taxonomies = [ 68 | {{ range .Taxonomies }}{ name = "{{ . }}"},{{ end }} 69 | ] 70 | 71 | [markdown] 72 | highlight_code = true 73 | render_emoji = true 74 | 75 | [extra] 76 | # this only affects the default "even" theme 77 | even_menu = [ 78 | {url = "$BASE_URL", name = "Home"}, 79 | {url = "$BASE_URL/tags", name = "Tags"}, 80 | ] 81 | ` 82 | 83 | type Zola struct { 84 | Title string 85 | BaseURL string 86 | ThemeName string 87 | ThemeRepo string 88 | Feed bool 89 | Taxonomies []string 90 | } 91 | 92 | func NewZola(title, baseURL, theme, themeRepo string, feed bool) *Zola { 93 | if theme == "" && themeRepo == "" { 94 | theme = zolaDefaultTheme 95 | themeRepo = zolaDefaultThemeRepo 96 | } 97 | 98 | return &Zola{ 99 | Title: title, 100 | BaseURL: baseURL, 101 | ThemeName: theme, 102 | ThemeRepo: themeRepo, 103 | Feed: feed, 104 | Taxonomies: []string{"tags"}, 105 | } 106 | } 107 | 108 | func (z *Zola) generateDir(path string) error { 109 | err := os.MkdirAll(path, os.ModeDir|0755) 110 | if err != nil { 111 | return errors.Wrap(err, "failed to create zola output directory") 112 | } 113 | 114 | for _, dir := range []string{"themes", "templates", "content"} { 115 | err = os.MkdirAll(filepath.Join(path, dir), os.ModeDir|0755) 116 | if err != nil { 117 | return errors.Wrapf(err, "failed to create zola %s directory", dir) 118 | } 119 | } 120 | return nil 121 | } 122 | 123 | func (z *Zola) downloadTheme(path string) error { 124 | return utils.CloneTheme(z.ThemeRepo, filepath.Join(path, "themes", z.ThemeName)) 125 | } 126 | 127 | func (z *Zola) generateConfig(path string) error { 128 | config, err := template.New("config").Parse(zolaConfigTemplate) 129 | if err != nil { 130 | return errors.Wrap(err, "failed to parse zola config template") 131 | } 132 | var configBuf bytes.Buffer 133 | err = config.Execute(&configBuf, z) 134 | if err != nil { 135 | return errors.Wrap(err, "failed to execute zola config template") 136 | } 137 | 138 | err = os.WriteFile(filepath.Join(path, "config.toml"), configBuf.Bytes(), 0644) 139 | if err != nil { 140 | return errors.Wrap(err, "failed to write zola config file") 141 | } 142 | return nil 143 | } 144 | 145 | func (z *Zola) generateIndex(path string) error { 146 | index, err := template.New("index").Parse(zolaIndexTemplate) 147 | if err != nil { 148 | return errors.Wrap(err, "failed to parse zola index template") 149 | } 150 | var indexBuf bytes.Buffer 151 | err = index.Execute(&indexBuf, z) 152 | if err != nil { 153 | return errors.Wrap(err, "failed to execute zola index template") 154 | } 155 | 156 | err = os.WriteFile(filepath.Join(path, "content", "_index.md"), indexBuf.Bytes(), 0644) 157 | if err != nil { 158 | return errors.Wrap(err, "failed to write zola index file") 159 | } 160 | return nil 161 | } 162 | 163 | func (z *Zola) generatePost(path string, issues []types.Issue) error { 164 | funcMap := template.FuncMap{ 165 | "toml_escape": utils.EscapeTOMLString, 166 | } 167 | post, err := template.New("post").Funcs(funcMap).Parse(zolaPostTemplate) 168 | if err != nil { 169 | return errors.Wrap(err, "failed to parse zola post template") 170 | } 171 | for _, issue := range issues { 172 | var postBuf bytes.Buffer 173 | err = post.Execute(&postBuf, issue) 174 | if err != nil { 175 | return errors.Wrap(err, "failed to execute zola post template") 176 | } 177 | err = os.WriteFile( 178 | filepath.Join(path, "content", fmt.Sprintf("issue-%d.md", issue.Number)), 179 | postBuf.Bytes(), 180 | 0644) 181 | if err != nil { 182 | return errors.Wrap(err, "failed to write zola post file") 183 | } 184 | } 185 | return nil 186 | } 187 | 188 | func (z *Zola) Generate(issues []types.Issue, outputDir string) error { 189 | path, err := filepath.Abs(outputDir) 190 | if err != nil { 191 | return errors.Wrapf(err, "failed to get the output absolute path for %s", outputDir) 192 | } 193 | 194 | for _, fn := range []func(path string) error{ 195 | z.generateDir, z.downloadTheme, z.generateConfig, z.generateIndex, 196 | } { 197 | if err = fn(path); err != nil { 198 | return err 199 | } 200 | } 201 | 202 | return z.generatePost(path, issues) 203 | } 204 | -------------------------------------------------------------------------------- /pkg/types/types.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type User struct { 4 | Login string `json:"login"` 5 | ID int `json:"id"` 6 | URL string `json:"html_url"` 7 | AvatarURL string `json:"avatar_url"` 8 | } 9 | 10 | type Reactions struct { 11 | URL string `json:"url"` 12 | TotalCount int `json:"total_count"` 13 | ThumbUp int `json:"+1"` 14 | ThumbDown int `json:"-1"` 15 | Laugh int `json:"laugh"` 16 | Hooray int `json:"hooray"` 17 | Confused int `json:"confused"` 18 | Heart int `json:"heart"` 19 | Rocket int `json:"rocket"` 20 | Eyes int `json:"eyes"` 21 | } 22 | 23 | type Label struct { 24 | ID int `json:"id"` 25 | Name string `json:"name"` 26 | Description string `json:"description"` 27 | Color string `json:"color"` 28 | Default bool `json:"default"` 29 | } 30 | 31 | type Comment struct { 32 | ID int `json:"id"` 33 | IssueURL string `json:"issue_url"` 34 | HTMLURL string `json:"html_url"` 35 | User User `json:"user"` 36 | CreatedAt string `json:"created_at"` 37 | UpdatedAt string `json:"updated_at"` 38 | Body string `json:"body"` 39 | Reactions Reactions `json:"reactions"` 40 | } 41 | 42 | type PullRequest struct { 43 | HTMLURL string `json:"html_url"` 44 | DiffURL string `json:"diff_url"` 45 | PatchURL string `json:"patch_url"` 46 | MergedAt string `json:"merged_at"` 47 | } 48 | 49 | type Issue struct { 50 | ID int `json:"id"` 51 | Number int `json:"number"` // issue number 52 | Title string `json:"title"` 53 | URL string `json:"html_url"` 54 | Body string `json:"body"` 55 | User User `json:"user"` 56 | State string `json:"state"` 57 | Locked bool `json:"locked"` 58 | Labels []Label `json:"labels"` 59 | CreatedAt string `json:"created_at"` 60 | UpdatedAt string `json:"updated_at"` 61 | ClosedAt string `json:"closed_at"` 62 | Comments []Comment `json:"-"` // ignore for un-marshalling 63 | Reactions Reactions `json:"reactions"` 64 | PullRequest *PullRequest `json:"pull_request,omitempty"` // used for filtering pull requests 65 | } 66 | -------------------------------------------------------------------------------- /pkg/utils/repo.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/cockroachdb/errors" 8 | git "github.com/go-git/go-git/v5" 9 | ) 10 | 11 | func DirExist(path string) (bool, error) { 12 | info, err := os.Stat(path) 13 | if err != nil { 14 | if os.IsNotExist(err) { 15 | return false, nil 16 | } 17 | return false, errors.Wrapf(err, "failed to stat the path %s", path) 18 | } 19 | return info.IsDir(), nil 20 | } 21 | 22 | func CloneTheme(repo, path string) error { 23 | exist, err := DirExist(path) 24 | if err != nil { 25 | return err 26 | } 27 | if exist { 28 | return nil 29 | } 30 | 31 | fmt.Printf("clone the theme from %s to %s\n", repo, path) 32 | _, err = git.PlainClone(path, false, &git.CloneOptions{ 33 | URL: fmt.Sprintf("https://github.com/%s", repo), 34 | }, 35 | ) 36 | if err != nil { 37 | return errors.Wrapf(err, "failed to clone the repo %s", repo) 38 | } 39 | return nil 40 | } 41 | -------------------------------------------------------------------------------- /pkg/utils/toml.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "unicode" 7 | ) 8 | 9 | // EscapeTOMLString escapes a string to be safely embedded in a TOML file. 10 | func EscapeTOMLString(s string) string { 11 | // 1. Try Multi-line Literal String ('''...''') 12 | // This is the cleanest if it doesn't contain the delimiter. 13 | if !strings.Contains(s, "'''") { 14 | return fmt.Sprintf("'''%s'''", s) 15 | } 16 | 17 | // 2. Fallback to Multi-line Basic String ("""...""") 18 | // This requires escaping internal " and \ characters, and control characters. 19 | var sb strings.Builder 20 | sb.WriteString(`"""`) 21 | for _, r := range s { 22 | switch r { 23 | case '\\': 24 | sb.WriteString(`\\`) 25 | case '"': 26 | sb.WriteString(`\"`) 27 | case '\b': 28 | sb.WriteString(`\b`) 29 | case '\t': 30 | sb.WriteString(`\t`) 31 | case '\n': 32 | // TOML spec: "All other Unicode characters are allowed." for multi-line basic strings. 33 | // So, writing raw '\n' is technically allowed for readability. 34 | sb.WriteRune(r) 35 | case '\f': 36 | sb.WriteString(`\f`) 37 | case '\r': 38 | sb.WriteString(`\r`) 39 | default: 40 | // Handle other non-printable ASCII characters or characters that might confuse parsers 41 | // (e.g., ASCII control characters U+0000-U+001F, U+007F) 42 | if (r >= '\x00' && r <= '\x1f') || r == '\x7f' || !unicode.IsPrint(r) { 43 | // Use \uXXXX for common Unicode escapes 44 | fmt.Fprintf(&sb, "\\u%04x", r) 45 | } else { 46 | sb.WriteRune(r) 47 | } 48 | } 49 | } 50 | 51 | sb.WriteString(`"""`) // End TOML basic string delimiter 52 | return sb.String() 53 | } 54 | -------------------------------------------------------------------------------- /pkg/version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import ( 4 | "encoding/json" 5 | "runtime" 6 | ) 7 | 8 | var ( 9 | Package = "github.com/kemingy/isite" 10 | 11 | version = "0.0.0+unknown" 12 | buildDate = "1970-01-01T00:00:00Z" 13 | gitCommit = "" 14 | gitTag = "" 15 | ) 16 | 17 | type Version struct { 18 | Version string `json:"version"` 19 | BuildDate string `json:"build_date"` 20 | GitCommit string `json:"git_commit"` 21 | GitTag string `json:"git_tag"` 22 | GoVersion string `json:"go_version"` 23 | Compiler string `json:"compiler"` 24 | Platform string `json:"platform"` 25 | } 26 | 27 | func (v *Version) PrettyString() string { 28 | str, _ := json.MarshalIndent(v, "", "\t") 29 | return string(str) 30 | } 31 | 32 | func GetVersion() string { 33 | if gitTag != "" { 34 | return gitTag 35 | } 36 | return version 37 | } 38 | 39 | func GetVersionInfo() Version { 40 | return Version{ 41 | Version: GetVersion(), 42 | BuildDate: buildDate, 43 | GitCommit: gitCommit, 44 | GitTag: gitTag, 45 | GoVersion: runtime.Version(), 46 | Compiler: runtime.Compiler, 47 | Platform: runtime.GOOS + "/" + runtime.GOARCH, 48 | } 49 | } 50 | --------------------------------------------------------------------------------