├── .codeclimate.yml ├── .dockerignore ├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug.yml │ └── config.yml ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── check-for-spammy-issues.yml │ ├── pull-request-first-comment.yaml │ ├── run-check.yaml │ ├── site-deploy.yaml │ └── tests.yaml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── COVERAGE.md ├── LICENSE ├── MAINTAINERS ├── README.md ├── go.mod ├── go.sum ├── main.go ├── main_test.go ├── netlify.toml ├── pkg ├── markdown │ ├── convert.go │ └── convert_test.go └── slug │ ├── generator.go │ └── generator_test.go ├── stale_repositories_test.go └── tmpl ├── assets ├── awesome-go.css ├── favicon │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── favicon.ico │ └── manifest.json ├── fonts │ ├── firasans.css │ ├── firasans.ttf │ ├── firasans.woff │ ├── firasans.woff2 │ ├── firasansbold.ttf │ ├── firasansbold.woff │ ├── firasansbold.woff2 │ ├── firasansbolditalic.ttf │ ├── firasansbolditalic.woff │ ├── firasansbolditalic.woff2 │ ├── firasansbook.ttf │ ├── firasansbook.woff │ ├── firasansbook.woff2 │ ├── firasansbookitalic.ttf │ ├── firasansbookitalic.woff │ ├── firasansbookitalic.woff2 │ ├── firasansextralight.ttf │ ├── firasansextralight.woff │ ├── firasansextralight.woff2 │ ├── firasansextralightitalic.ttf │ ├── firasansextralightitalic.woff │ ├── firasansextralightitalic.woff2 │ ├── firasansitalic.ttf │ ├── firasansitalic.woff │ ├── firasansitalic.woff2 │ ├── firasanslight.ttf │ ├── firasanslight.woff │ ├── firasanslight.woff2 │ ├── firasanslightitalic.ttf │ ├── firasanslightitalic.woff │ ├── firasanslightitalic.woff2 │ ├── firasansmedium.ttf │ ├── firasansmedium.woff │ ├── firasansmedium.woff2 │ ├── firasansmediumitalic.ttf │ ├── firasansmediumitalic.woff │ ├── firasansmediumitalic.woff2 │ ├── firasanssemibold.ttf │ ├── firasanssemibold.woff │ ├── firasanssemibold.woff2 │ ├── firasanssemibolditalic.ttf │ ├── firasanssemibolditalic.woff │ └── firasanssemibolditalic.woff2 ├── logo.png ├── normalize.css └── sponsors │ └── doppler.png ├── category-index.tmpl.html ├── index.tmpl.html ├── robots.txt └── sitemap.tmpl.xml /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | engines: 3 | rubocop: 4 | enabled: true 5 | golint: 6 | enabled: true 7 | gofmt: 8 | enabled: true 9 | govet: 10 | enabled: true 11 | fixme: 12 | enabled: true 13 | duplication: 14 | enabled: true 15 | config: 16 | languages: 17 | - go 18 | ratings: 19 | paths: 20 | - "**.go" 21 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | tmpl/assets/* linguist-vendored 2 | *.js linguist-vendored 3 | *.css linguist-vendored 4 | *.html linguist-vendored 5 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: avelino 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Report a bug encountered 3 | labels: ["bug", "pending-review"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thank you very much for opening a bug report at awesome-go. 9 | 10 | If you have a feature idea or need help, please go to [our Forum](https://github.com/avelino/awesome-go/discussions). 11 | before opening the issue we recommend that you read our [contribution guide](https://github.com/avelino/awesome-go/blob/main/CONTRIBUTING.md), there we talk about how you can contribute to awesome-go. 12 | - type: checkboxes 13 | id: confirm-search 14 | attributes: 15 | label: Search first 16 | description: Please search [existing issues](https://github.com/avelino/awesome-go/issues) and the [awesome-go forum](https://github.com/avelino/awesome-go/discussions) before reporting. 17 | options: 18 | - label: I searched and no similar issues were found 19 | required: true 20 | - type: textarea 21 | id: problem 22 | attributes: 23 | label: What Happened? 24 | description: | 25 | Please provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner. 26 | validations: 27 | required: true 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | contact_links: 2 | - name: Feature request 3 | url: https://github.com/avelino/awesome-go/discussions/new?category=ideas 4 | about: Suggest an idea for awesome-go 5 | - name: Questions & Help 6 | url: https://github.com/avelino/awesome-go/discussions/new?category=q-a 7 | about: Ask a question about awesome-go 8 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## We want to ensure high quality of the packages. Make sure that you've checked the boxes below before sending a pull request. 2 | 3 | - [ ] I have read the [Contribution Guidelines](https://github.com/avelino/awesome-go/blob/main/CONTRIBUTING.md#contribution-guidelines) 4 | - [ ] I have read the [Maintainers Note](https://github.com/avelino/awesome-go/blob/main/CONTRIBUTING.md#maintainers) 5 | - [ ] I have read the [Quality Standards](https://github.com/avelino/awesome-go/blob/main/CONTRIBUTING.md#quality-standards) 6 | 7 | _Not every repository (project) will require every option, but most projects should. Check the Contribution Guidelines for details._ 8 | 9 | - [ ] The repo documentation has a pkg.go.dev link. 10 | - [ ] The repo documentation has a coverage service link. 11 | - [ ] The repo documentation has a goreportcard link. 12 | - [ ] The repo has a version-numbered release and a go.mod file. 13 | - [ ] The repo has a continuous integration process that automatically runs tests that must pass before new pull requests are merged. 14 | - [ ] Continuous integration is used to attempt to catch issues prior to releasing this package to end-users. 15 | 16 | ## Please provide some links to your package to ease the review 17 | 18 | - [ ] forge link (github.com, gitlab.com, etc): 19 | - [ ] pkg.go.dev: 20 | - [ ] goreportcard.com: 21 | - [ ] coverage service link ([codecov](https://codecov.io/), [coveralls](https://coveralls.io/), etc.): 22 | 23 | ## Pull Request content 24 | 25 | - [ ] The package has been added to the list in alphabetical order. 26 | - [ ] The package has an appropriate description with correct grammar. 27 | - [ ] As far as I know, the package has not been listed here before. 28 | 29 | ## Category quality 30 | 31 | _Note that new categories can be added only when there are 3 packages or more._ 32 | 33 | Packages added a long time ago might not meet the current guidelines anymore. It would be very helpful if you could check 3-5 packages above and below your submission to ensure that they also still meet the Quality Standards. 34 | 35 | Please delete one of the following lines: 36 | 37 | - [ ] The packages around my addition still meet the Quality Standards. 38 | - [ ] I removed the following packages around my addition: (please give a short reason for each removal) 39 | 40 | Thanks for your PR, you're awesome! :sunglasses: 41 | -------------------------------------------------------------------------------- /.github/workflows/check-for-spammy-issues.yml: -------------------------------------------------------------------------------- 1 | name: Issues spammy check 2 | on: 3 | issues: 4 | types: [opened] 5 | 6 | jobs: 7 | mark-as-spam: 8 | name: Remove issues with spammy 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: close issue 12 | uses: balevine/mark-as-spam@v1.0 13 | env: 14 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 15 | -------------------------------------------------------------------------------- /.github/workflows/pull-request-first-comment.yaml: -------------------------------------------------------------------------------- 1 | name: First comment in new pull request 2 | 3 | on: 4 | pull_request_target: 5 | types: [opened] 6 | 7 | jobs: 8 | commentCreated: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | pull-requests: write 12 | issues: write 13 | environment: action 14 | env: 15 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 16 | steps: 17 | - name: first comment 18 | uses: peter-evans/create-or-update-comment@v3 19 | with: 20 | token: ${{ secrets.GITHUB_TOKEN }} 21 | issue-number: ${{ github.event.pull_request.number }} 22 | body: | 23 | Thank you for contributing to [awesome-go](https://awesome-go.com/). We will review your contribution as soon as possible. 24 | 25 | Make sure you add the links in the body of the pull request that are requested in the [contribution guide](https://github.com/avelino/awesome-go/blob/main/CONTRIBUTING.md): 26 | - repo link 27 | - pkg.go.dev 28 | - goreportcard.com 29 | - coverage 30 | 31 | > Your project is under review. It may take a few days to be approved. 32 | -------------------------------------------------------------------------------- /.github/workflows/run-check.yaml: -------------------------------------------------------------------------------- 1 | name: Check For Stale Repositories 2 | on: 3 | workflow_dispatch: 4 | schedule: 5 | - cron: '0 0 * * 0' 6 | 7 | permissions: 8 | contents: read # to fetch code (actions/checkout) 9 | 10 | jobs: 11 | build: 12 | name: Running test 13 | runs-on: ubuntu-latest 14 | container: golang:latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Get dependencies 18 | run: go get -v -t -d ./... 19 | - name: run script 20 | run: go test -v -run ^TestStaleRepository$ 21 | env: 22 | OAUTH_TOKEN: ${{secrets.OAUTH_TOKEN}} 23 | -------------------------------------------------------------------------------- /.github/workflows/site-deploy.yaml: -------------------------------------------------------------------------------- 1 | name: site-deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - "main" 7 | 8 | permissions: 9 | contents: read # to fetch code (actions/checkout) 10 | 11 | jobs: 12 | build: 13 | name: Make and Deploy site 14 | runs-on: ubuntu-latest 15 | environment: netlify 16 | container: golang:latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Get dependencies 20 | run: go get -v -t -d ./... 21 | - name: Make awesome-go.com 22 | run: go run . 23 | - name: Setup node 24 | uses: actions/setup-node@v4 25 | with: 26 | node-version: 20 27 | - name: deploy awesome-go.com 28 | uses: nwtgck/actions-netlify@v3.0 29 | with: 30 | publish-dir: "./out" 31 | production-branch: main 32 | production-deploy: true 33 | enable-pull-request-comment: false 34 | enable-commit-comment: false 35 | enable-commit-status: false 36 | overwrites-pull-request-comment: false 37 | env: 38 | NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} 39 | NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} 40 | timeout-minutes: 1 41 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | pull_request: 8 | 9 | permissions: 10 | contents: read # to fetch code (actions/checkout) 11 | 12 | jobs: 13 | build: 14 | name: Running test 15 | runs-on: ubuntu-latest 16 | container: golang:latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Get dependencies 20 | run: go get -v -t -d ./... 21 | - name: Run tests 22 | run: go test main_test.go main.go 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out/ 2 | awesome-go 3 | 4 | # Folders 5 | .idea 6 | .vscode 7 | test_stale_repositories_log 8 | *.exe 9 | # Local Netlify folder 10 | .netlify 11 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## 1. Purpose 4 | 5 | A primary goal of Awesome Go is to be inclusive to the largest number of contributors, with the most varied and diverse backgrounds possible. As such, we are committed to providing a friendly, safe and welcoming environment for all, regardless of gender, sexual orientation, ability, ethnicity, socioeconomic status, and religion (or lack thereof). 6 | 7 | This code of conduct outlines our expectations for all those who participate in our community, as well as the consequences for unacceptable behavior. 8 | 9 | We invite all those who participate in Awesome Go to help us create safe and positive experiences for everyone. 10 | 11 | ## 2. Open Source Citizenship 12 | 13 | A supplemental goal of this Code of Conduct is to increase open source citizenship by encouraging participants to recognize and strengthen the relationships between our actions and their effects on our community. 14 | 15 | Communities mirror the societies in which they exist, and positive action is essential to counteract the many forms of inequality and abuses of power that exist in society. 16 | 17 | If you see someone who is making an extra effort to ensure our community is welcoming, friendly, and encourages all participants to contribute to the fullest extent, we want to know. 18 | 19 | ## 3. Expected Behavior 20 | 21 | The following behaviors are expected and requested of all community members: 22 | 23 | * Participate in an authentic and active way. In doing so, you contribute to the health and longevity of this community. 24 | * Exercise consideration and respect in your speech and actions. 25 | * Attempt collaboration before conflict. 26 | * Refrain from demeaning, discriminatory, or harassing behavior and speech. 27 | * Be mindful of your surroundings and of your fellow participants. Alert community leaders if you notice a dangerous situation, someone distressed, or violations of this Code of Conduct, even if they seem inconsequential. 28 | * Remember that community event venues may be shared with members of the public; please be respectful to all patrons of these locations. 29 | 30 | ## 4. Unacceptable Behavior 31 | 32 | The following behaviors are considered harassment and are unacceptable within our community: 33 | 34 | * Violence, threats of violence or violent language directed against another person. 35 | * Sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory jokes and language. 36 | * Posting or displaying sexually explicit or violent material. 37 | * Posting or threatening to post other people’s personally identifying information ("doxing"). 38 | * Personal insults, particularly those related to gender, sexual orientation, race, religion, or disability. 39 | * Inappropriate photography or recording. 40 | * Inappropriate physical contact. You should have someone’s consent before touching them. 41 | * Unwelcome sexual attention. This includes, sexualized comments or jokes; inappropriate touching, groping, and unwelcomed sexual advances. 42 | * Deliberate intimidation, stalking or following (online or in person). 43 | * Advocating for, or encouraging, any of the above behavior. 44 | * Sustained disruption of community events, including talks and presentations. 45 | 46 | ## 5. Consequences of Unacceptable Behavior 47 | 48 | Unacceptable behavior from any community member, including sponsors and those with decision-making authority, will not be tolerated. 49 | 50 | Anyone asked to stop unacceptable behavior is expected to comply immediately. 51 | 52 | If a community member engages in unacceptable behavior, the community organizers may take any action they deem appropriate, up to and including a temporary ban or permanent expulsion from the community unexpected (and without refund in the case of a paid event). 53 | 54 | ## 6. Reporting Guidelines 55 | 56 | If you are subject to or witness unacceptable behavior, or have any other concerns, please notify a community organizer as soon as possible. 57 | 58 | [Reporting Guidelines](https://github.com/avelino/awesome-go/blob/main/CONTRIBUTING.md#contribution-guidelines) 59 | 60 | Additionally, community organizers are available to help community members engage with local law enforcement or to otherwise help those experiencing unacceptable behavior feel safe. In the context of in-person events, organizers will also provide escorts as desired by the person experiencing distress. 61 | 62 | ## 7. Addressing Grievances 63 | 64 | If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should notify Avelino with a concise description of your grievance. Your grievance will be handled in accordance with our existing governing policies. 65 | 66 | [Policy](https://github.com/avelino/awesome-go/blob/main/CONTRIBUTING.md) 67 | 68 | ## 8. Scope 69 | 70 | We expect all community participants (contributors, paid or otherwise; sponsors; and other guests) to abide by this Code of Conduct in all community venues–online and in-person–as well as in all one-on-one communications pertaining to community business. 71 | 72 | This code of conduct and its related procedures also applies to unacceptable behavior occurring outside the scope of community activities when such behavior has the potential to adversely affect the safety and well-being of community members. 73 | 74 | ## 9. Contact info 75 | 76 | avelinorun AT gmail DOT com 77 | 78 | ## 10. License and attribution 79 | 80 | This Code of Conduct is distributed under a [Creative Commons Attribution-ShareAlike license](http://creativecommons.org/licenses/by-sa/3.0/). 81 | 82 | Portions of text derived from the [Django Code of Conduct](https://www.djangoproject.com/conduct/) and the [Geek Feminism Anti-Harassment Policy](http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Policy). 83 | 84 | Retrieved on November 22, 2016 85 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | This resource was made by the Go community and wouldn't be possible without you! 2 | We appreciate and recognize [all contributors](https://github.com/avelino/awesome-go/graphs/contributors). 3 | 4 | 5 | # Contribution Guidelines 6 | 7 | > Please be aware that we want to accept your contribution, but we have **some rules to keep the minimum quality** of the packages listed here. All reviews are **not personal feedback**, even if you are a _developer reviewing your contribution_. **Sorry, if we can't meet your expectations; we do our best**. 8 | 9 | - **To add, remove, or change things on the list:** Submit a pull request 10 | 11 | To set this list apart from and complement the excellent [Go wiki Projects page](https://golang.org/wiki/Projects), 12 | and other lists, awesome-go is a specially curated list of high-quality, actively maintained Go packages and resources. 13 | 14 | Please contribute links to packages/projects you have used or are familiar with. This will help ensure high-quality entries. 15 | 16 | > the maintainers do not work full-time on the project, meaning that we do not have a set periodicity for reviewing contributions - rest assured that we will do our best to review and eventually accept contributions 17 | 18 | 19 | ## Quality standards 20 | 21 | To be on the list, project repositories should adhere to the following quality standards. 22 | (https://goreportcard.com/report/github.com/ **github_user** / **github_repo**): 23 | 24 | - have at least 5 months of history since the first commit. 25 | - have an **open source license**, [see list of allowed licenses](https://opensource.org/licenses/alphabetical); 26 | - function as documented and expected; 27 | - be generally useful to the wider community of Go programmers; 28 | - be actively maintained with: 29 | - regular, recent commits; 30 | - or, for finished projects, issues and pull requests are responded to generally within 2 weeks; 31 | - be stable or progressing toward stable; 32 | - be thoroughly documented (README, pkg.go.dev doc comments, etc.) in the English language, so everyone is able to understand the project's intention and how it works. All public functions and types should have a Go-style documentation header; 33 | - if the library/program is testable, then coverage should be >= 80% for non-data-related packages and >=90% for data-related packages. (**Note**: the tests will be reviewed too. We will check your coverage manually if your package's coverage is just a benchmark result); 34 | - have at least one official version-numbered release that allows go.mod files to list the file by version number of the form vX.X.X. 35 | 36 | Categories must have at least 3 items. 37 | 38 | 39 | ## Preparing for review 40 | 41 | Projects listed must have the following in their documentation. When submitting, you will be asked 42 | to provide them. 43 | 44 | - A link to the project's pkg.go.dev page 45 | - A link to the project's Go Report Card report 46 | - A link to a code coverage report 47 | 48 | One way to accomplish the above is to add badges to your project's README file. 49 | - Use https://pkg.go.dev/badge/ to create the pkg.go.dev link. 50 | - Go to https://goreportcard.com/ to generate a Go Report Card report, then click on the report badge in the upper-right corner to see details on how to add the badge to your README. 51 | - Codecov, coveralls, and gocover all offer ways to create badges for code coverage reports. Another option is to generate a badge as part of a continuous integration process. See [Code Coverage](COVERAGE.md) for an example. 52 | 53 | 54 | ## How to add an item to the list 55 | 56 | Open a pull request against the README.md document that adds the repository to the list. 57 | 58 | - The pull request should add one and only one item to the list. 59 | - The added item should be in alphabetical order within its category. 60 | - The link should be the name of the package or project. 61 | - Descriptions should be clear, concise, and non-promotional. 62 | - Descriptions should follow the link on the same line and end with a punctuation mark. 63 | - Remember to put a period `.` at the end of the project description. 64 | 65 | If you are creating a new category, move the projects that apply to the new category, ensuring 66 | that the resulting list has at least 3 projects in every category, and that the categories are alphabetized. 67 | 68 | Fill out the template in your PR with the links asked for. If you accidentally remove the PR template from the submission, you can find it [here](https://github.com/avelino/awesome-go/blob/main/.github/PULL_REQUEST_TEMPLATE.md). 69 | 70 | 71 | ## Congrats, your project got accepted - what now? 72 | You are an outstanding project now! Feel encouraged to tell others about it by adding one of these badges: 73 | [![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go) 74 | [![Mentioned in Awesome Go](https://awesome.re/mentioned-badge-flat.svg)](https://github.com/avelino/awesome-go) 75 | 76 | ```md 77 | [![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go) 78 | [![Mentioned in Awesome Go](https://awesome.re/mentioned-badge-flat.svg)](https://github.com/avelino/awesome-go) 79 | ``` 80 | 81 | 82 | ## Maintenance expectations for projects listed here 83 | 84 | To prevent removal from awesome-go, your project must maintain the following quality standards. 85 | - Development should be ongoing and maintain code quality. Official releases should be at least once a year if the project is ongoing. 86 | - Or, if development has halted because the project is mature and stable, that can be demonstrated by having no bug reports in the Issues list that are older than 6 months. 87 | - All links to quality reports should be to the most recent official release or current ongoing development. 88 | 89 | Highly recommended but not required: 90 | - A continuous integration process to be part of the ongoing development process 91 | - That the project uses a pull-request process, and the owners do not commit directly to the repository 92 | - That the pull-request process requires the continuous-integration tests to pass before a pull request can be merged 93 | 94 | 95 | ## How to remove an item from the list 96 | 97 | - Open a pull request that deletes the line of the project in question. 98 | - Delete the submission template and substitute a description of which criteria the project is not meeting. It should be a combination of the following. 99 | - The project has not made an official release within the last year and has open issues. 100 | - The project is not responding to bug reports issued within 6 months of submission. 101 | - The project is not meeting quality standards as indicated by the Go Report Card or Code Coverage tests. 102 | - The quality standard links have been removed from the documentation. 103 | - The project is no longer open-sourced. 104 | - The project is incompatible with any Go version issued within the last year (there is hopefully an open PR about this at the project). 105 | 106 | If the project is hosted on GitHub, include a link to the project's submitter and/or author so 107 | that they will be notified of the desire to remove the project and have an opportunity to respond. 108 | The link should be of the form @githubID. 109 | 110 | If the project is not hosted on GitHub, open an issue at the project in question's repository linking to the PR 111 | and stating the following: 112 | 113 | >This project is currently listed at awesome-go at https://github.com/avelino/awesome-go. 114 | However, it appears that the project is not maintaining the quality standards required to continue to be listed at the awesome-go project. 115 | This project is scheduled to be removed within 2 weeks of this posting. To continue to be listed at awesome-go, please respond at: 116 | -- link to above PR -- 117 | 118 | Then, comment on your PR at awesome-go with a link to the removal issue at the project. 119 | 120 | 121 | ## Maintainers 122 | 123 | To make sure every PR is checked, we have [team maintainers](MAINTAINERS). Every PR MUST be reviewed by at least one maintainer before it can get merged. 124 | 125 | The maintainers will review your PR and notify you and tag it in case any 126 | information is still missing. They will wait 15 days for your interaction, after 127 | that the PR will be closed. 128 | 129 | 130 | ## Reporting issues 131 | 132 | Please open an issue if you would like to discuss anything that could be improved or have suggestions for making the list a more valuable resource. We realize sometimes packages fall into abandonment or have breaking builds for extended periods of time, so if you see that, feel free to change its listing, or please let us know. We also realize that sometimes projects are just going through transitions or are more experimental in nature. These can still be cool, but we can indicate them as transitory or experimental. 133 | 134 | Removal changes will not be applied until they have been pending for a minimum of 1 week (7 days). This grace window benefits projects that may be going through a temporary transition, but are otherwise worthy of being on the list. 135 | 136 | Thanks, everyone! 137 | 138 | 139 | ## How decisions are made 140 | 141 | The official group of maintainers has the final decision on what PRs are accepted. Discussions are made openly in issues. Decisions are made by consensus. 142 | 143 | 144 | ## How to become a contributor? 145 | 146 | awesome-go is an open source project (created and maintained by the community), we are always open to new people to help us review the contributions (pull requests), **you don't need permission** or _name on the maintainers list_ to review a contribution and mark it as **LGTM**. 147 | 148 | > Before you do anything, please read [this topic](https://github.com/avelino/awesome-go/blob/main/CONTRIBUTING.md#quality-standards) very carefully. 149 | 150 | Now that you've read it, let's go! 151 | 152 | Go into the pull requests (PR) and look at the following aspects: 153 | 154 | * **shared links in the body of the PR:** they need to be valid and follow the quality specified above 155 | * **check that the link added to `README.md`** is the same as the link to the repository mentioned in the body of the PR. 156 | * **is it in the correct category?** 157 | 158 | If everything is OK, mark the PR as approved, [read this documentation](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/reviewing-proposed-changes-in-a-pull-request#starting-a-review) on how to do it. 159 | 160 | **Welcome to awesome-go!** 161 | 162 | 163 | ## How to become an ~~"official maintainer"~~? 164 | 165 | We don't give this name to people who are allowed to accept the PR. 166 | 167 | If you are a person who is constantly active in reviewing PR and contributing to the project, you will be invited by a maintainer. 168 | 169 | > **remember:** if you stop contributing with awesome-go for a long time, you will automatically be removed from the list of maintainers. 170 | -------------------------------------------------------------------------------- /COVERAGE.md: -------------------------------------------------------------------------------- 1 | # Code Coverage 2 | 3 | While we recommend using one of the free websites available for monitoring code coverage during your continuous integration process, below is an example of how you can incorporate code coverage during the continuous integration process provided by GitHub actions and generate a code coverage report without one of those services. 4 | 5 | This `yaml` file will run tests on multiple system configurations, but will produce a code coverage report on only one of those. It will then create a code coverage badge and add it to the README file. 6 | 7 | This file should be put in the `.github/workflows` directory of your repo: 8 | 9 | ```yaml 10 | name: Go # The name of the workflow that will appear on Github 11 | 12 | on: 13 | push: 14 | branches: [ main ] 15 | pull_request: 16 | branches: [ main ] 17 | # Allows you to run this workflow manually from the Actions tab 18 | workflow_dispatch: 19 | 20 | jobs: 21 | 22 | build: 23 | runs-on: ${{ matrix.os }} 24 | strategy: 25 | matrix: 26 | os: [ubuntu-latest, windows-latest] 27 | go: [1.16, 1.17] 28 | steps: 29 | - uses: actions/checkout@v2 30 | 31 | - name: Set up Go 32 | uses: actions/setup-go@v2 33 | with: 34 | go-version: ${{ matrix.go }} 35 | 36 | - name: Build 37 | run: go install 38 | 39 | - name: Test 40 | run: | 41 | go test -v -cover ./... -coverprofile coverage.out -coverpkg ./... 42 | go tool cover -func coverage.out -o coverage.out # Replaces coverage.out with the analysis of coverage.out 43 | 44 | - name: Go Coverage Badge 45 | uses: tj-actions/coverage-badge-go@v1 46 | if: ${{ runner.os == 'Linux' && matrix.go == '1.17' }} # Runs this on only one of the ci builds. 47 | with: 48 | green: 80 49 | filename: coverage.out 50 | 51 | - uses: stefanzweifel/git-auto-commit-action@v4 52 | id: auto-commit-action 53 | with: 54 | commit_message: Apply Code Coverage Badge 55 | skip_fetch: true 56 | skip_checkout: true 57 | file_pattern: ./README.md 58 | 59 | - name: Push Changes 60 | if: steps.auto-commit-action.outputs.changes_detected == 'true' 61 | uses: ad-m/github-push-action@master 62 | with: 63 | github_token: ${{ github.token }} 64 | branch: ${{ github.ref }} 65 | 66 | ``` 67 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Thiago Avelino 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 all 13 | 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 THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /MAINTAINERS: -------------------------------------------------------------------------------- 1 | Avelino (@avelino) 2 | Duke (@dukex) 3 | Dmitri Shuralyov (@dmitshur) 4 | Dobrosław Żybort (@matrixik) 5 | Dean Karn (@joeybloggs) 6 | Kirill Danshin (@kirillDanshin) 7 | Felipe Oliveira (@felipeweb) 8 | Bo-Yi Wu (@appleboy) 9 | Cássio Botaro (@cassiobotaro) 10 | Jessica Temporal (@jtemporal) 11 | Ceriath (@ceriath) 12 | Andy Pan (@panjf2000) 13 | Phani Rithvij (@phanirithvij) 14 | Yassine Benaid (@yassinebenaid) 15 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/avelino/awesome-go 2 | 3 | go 1.21 4 | toolchain go1.24.1 5 | 6 | require ( 7 | github.com/PuerkitoBio/goquery v1.8.1 8 | github.com/avelino/slugify v0.0.0-20180501145920-855f152bd774 9 | github.com/otiai10/copy v1.14.0 10 | github.com/yuin/goldmark v1.6.0 11 | golang.org/x/oauth2 v0.15.0 12 | ) 13 | 14 | require ( 15 | github.com/andybalholm/cascadia v1.3.1 // indirect 16 | github.com/golang/protobuf v1.5.3 // indirect 17 | golang.org/x/net v0.38.0 // indirect 18 | golang.org/x/sync v0.12.0 // indirect 19 | golang.org/x/sys v0.31.0 // indirect 20 | golang.org/x/text v0.23.0 // indirect 21 | google.golang.org/appengine v1.6.7 // indirect 22 | google.golang.org/protobuf v1.33.0 // indirect 23 | ) 24 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM= 2 | github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ= 3 | github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= 4 | github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= 5 | github.com/avelino/slugify v0.0.0-20180501145920-855f152bd774 h1:HrMVYtly2IVqg9EBooHsakQ256ueojP7QuG32K71X/U= 6 | github.com/avelino/slugify v0.0.0-20180501145920-855f152bd774/go.mod h1:5wi5YYOpfuAKwL5XLFYopbgIl/v7NZxaJpa/4X6yFKE= 7 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 8 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 9 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 10 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 11 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 12 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 13 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 14 | github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= 15 | github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= 16 | github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks= 17 | github.com/otiai10/mint v1.5.1/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM= 18 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 19 | github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68= 20 | github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 21 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 22 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 23 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 24 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 25 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 26 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 27 | golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 28 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 29 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 30 | golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= 31 | golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= 32 | golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= 33 | golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= 34 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 35 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 36 | golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= 37 | golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 38 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 39 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 40 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 41 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 42 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 43 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 44 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 45 | golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= 46 | golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 47 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 48 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 49 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 50 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 51 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 52 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 53 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 54 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 55 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 56 | golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= 57 | golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= 58 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 59 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 60 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 61 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 62 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 63 | google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= 64 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 65 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 66 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 67 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 68 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 69 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Package main contains code for generate static site. 2 | package main 3 | 4 | import ( 5 | "bytes" 6 | "embed" 7 | "errors" 8 | "fmt" 9 | template2 "html/template" 10 | "net/url" 11 | "os" 12 | "path/filepath" 13 | "text/template" 14 | 15 | "github.com/avelino/awesome-go/pkg/markdown" 16 | cp "github.com/otiai10/copy" 17 | 18 | "github.com/PuerkitoBio/goquery" 19 | "github.com/avelino/awesome-go/pkg/slug" 20 | ) 21 | 22 | // Link contains info about awesome url 23 | type Link struct { 24 | Title string 25 | URL string 26 | Description string 27 | } 28 | 29 | // Category describe link category 30 | type Category struct { 31 | Title string 32 | Slug string 33 | Description string 34 | Links []Link 35 | } 36 | 37 | // Source files 38 | const readmePath = "README.md" 39 | 40 | // This files should be copied 'as is' to outDir directory 41 | var staticFiles = []string{ 42 | "tmpl/assets", 43 | "tmpl/robots.txt", 44 | } 45 | 46 | // Templates 47 | //go:embed tmpl/*.tmpl.html tmpl/*.tmpl.xml 48 | var tplFs embed.FS 49 | 50 | var tpl = template.Must(template.ParseFS(tplFs, "tmpl/*.tmpl.html", "tmpl/*.tmpl.xml")) 51 | 52 | // Output files 53 | const outDir = "out/" // NOTE: trailing slash is required 54 | 55 | var outIndexFile = filepath.Join(outDir, "index.html") 56 | var outSitemapFile = filepath.Join(outDir, "sitemap.xml") 57 | 58 | func main() { 59 | if err := buildStaticSite(); err != nil { 60 | panic(err) 61 | } 62 | } 63 | 64 | func buildStaticSite() error { 65 | if err := dropCreateDir(outDir); err != nil { 66 | return fmt.Errorf("drop-create out dir: %w", err) 67 | } 68 | 69 | if err := renderIndex(readmePath, outIndexFile); err != nil { 70 | return fmt.Errorf("convert markdown to html: %w", err) 71 | } 72 | 73 | input, err := os.ReadFile(outIndexFile) 74 | if err != nil { 75 | return fmt.Errorf("read converted html: %w", err) 76 | } 77 | 78 | doc, err := goquery.NewDocumentFromReader(bytes.NewReader(input)) 79 | if err != nil { 80 | return fmt.Errorf("create goquery instance: %w", err) 81 | } 82 | 83 | categories, err := extractCategories(doc) 84 | if err != nil { 85 | return fmt.Errorf("extract categories: %w", err) 86 | } 87 | 88 | if err := renderCategories(categories); err != nil { 89 | return fmt.Errorf("render categories: %w", err) 90 | } 91 | 92 | if err := rewriteLinksInIndex(doc, categories); err != nil { 93 | return fmt.Errorf("rewrite links in index: %w", err) 94 | } 95 | 96 | if err := renderSitemap(categories); err != nil { 97 | return fmt.Errorf("render sitemap: %w", err) 98 | } 99 | 100 | for _, srcFilename := range staticFiles { 101 | dstFilename := filepath.Join(outDir, filepath.Base(srcFilename)) 102 | fmt.Printf("Copy static file: %s -> %s\n", srcFilename, dstFilename) 103 | if err := cp.Copy(srcFilename, dstFilename); err != nil { 104 | return fmt.Errorf("copy static file `%s` to `%s`: %w", srcFilename, dstFilename, err) 105 | } 106 | } 107 | 108 | return nil 109 | } 110 | 111 | // dropCreateDir drop and create output directory 112 | func dropCreateDir(dir string) error { 113 | if err := os.RemoveAll(dir); err != nil { 114 | return fmt.Errorf("remove dir: %w", err) 115 | } 116 | 117 | if err := mkdirAll(dir); err != nil { 118 | return fmt.Errorf("create dir: %w", err) 119 | } 120 | 121 | return nil 122 | } 123 | 124 | func mkdirAll(path string) error { 125 | _, err := os.Stat(path) 126 | // directory is exists 127 | if err == nil { 128 | return nil 129 | } 130 | 131 | // unexpected error 132 | if !os.IsNotExist(err) { 133 | return fmt.Errorf("unexpected result of dir stat: %w", err) 134 | } 135 | 136 | // directory is not exists 137 | if err := os.MkdirAll(path, 0755); err != nil { 138 | return fmt.Errorf("midirAll: %w", err) 139 | } 140 | 141 | return nil 142 | } 143 | 144 | func renderCategories(categories map[string]Category) error { 145 | for _, category := range categories { 146 | categoryDir := filepath.Join(outDir, category.Slug) 147 | if err := mkdirAll(categoryDir); err != nil { 148 | return fmt.Errorf("create category dir `%s`: %w", categoryDir, err) 149 | } 150 | 151 | // FIXME: embed templates 152 | categoryIndexFilename := filepath.Join(categoryDir, "index.html") 153 | fmt.Printf("Write category Index file: %s\n", categoryIndexFilename) 154 | 155 | buf := bytes.NewBuffer(nil) 156 | if err := tpl.Lookup("category-index.tmpl.html").Execute(buf, category); err != nil { 157 | return fmt.Errorf("render category `%s`: %w", categoryDir, err) 158 | } 159 | 160 | // Sanitize HTML. This is not necessary, but allows to have content 161 | // of all html files in same style. 162 | { 163 | doc, err := goquery.NewDocumentFromReader(buf) 164 | if err != nil { 165 | return fmt.Errorf("create goquery instance for `%s`: %w", categoryDir, err) 166 | } 167 | 168 | html, err := doc.Html() 169 | if err != nil { 170 | return fmt.Errorf("render goquery html for `%s`: %w", categoryDir, err) 171 | } 172 | 173 | if err := os.WriteFile(categoryIndexFilename, []byte(html), 0644); err != nil { 174 | return fmt.Errorf("write category file `%s`: %w", categoryDir, err) 175 | } 176 | } 177 | } 178 | 179 | return nil 180 | } 181 | 182 | func renderSitemap(categories map[string]Category) error { 183 | f, err := os.Create(outSitemapFile) 184 | if err != nil { 185 | return fmt.Errorf("create sitemap file `%s`: %w", outSitemapFile, err) 186 | } 187 | 188 | fmt.Printf("Render Sitemap to: %s\n", outSitemapFile) 189 | 190 | if err := tpl.Lookup("sitemap.tmpl.xml").Execute(f, categories); err != nil { 191 | return fmt.Errorf("render sitemap: %w", err) 192 | } 193 | 194 | return nil 195 | } 196 | 197 | func extractCategories(doc *goquery.Document) (map[string]Category, error) { 198 | categories := make(map[string]Category) 199 | var rootErr error 200 | 201 | doc. 202 | Find("body #contents"). 203 | NextFiltered("ul"). 204 | Find("ul"). 205 | EachWithBreak(func(_ int, selUl *goquery.Selection) bool { 206 | if rootErr != nil { 207 | return false 208 | } 209 | 210 | selUl. 211 | Find("li a"). 212 | EachWithBreak(func(_ int, s *goquery.Selection) bool { 213 | selector, exists := s.Attr("href") 214 | if !exists { 215 | return true 216 | } 217 | 218 | category, err := extractCategory(doc, selector) 219 | if err != nil { 220 | rootErr = fmt.Errorf("extract category: %w", err) 221 | return false 222 | } 223 | 224 | categories[selector] = *category 225 | 226 | return true 227 | }) 228 | 229 | return true 230 | }) 231 | 232 | if rootErr != nil { 233 | return nil, fmt.Errorf("extract categories: %w", rootErr) 234 | } 235 | 236 | return categories, nil 237 | } 238 | 239 | func extractCategory(doc *goquery.Document, selector string) (*Category, error) { 240 | var category Category 241 | var err error 242 | 243 | doc.Find(selector).EachWithBreak(func(_ int, selCatHeader *goquery.Selection) bool { 244 | selDescr := selCatHeader.NextFiltered("p") 245 | // FIXME: bug. this would select links from all neighboring 246 | // sub-categories until the next category. To prevent this we should 247 | // find only first ul 248 | ul := selCatHeader.NextFilteredUntil("ul", "h2") 249 | 250 | var links []Link 251 | ul.Find("li").Each(func(_ int, selLi *goquery.Selection) { 252 | selLink := selLi.Find("a") 253 | url, _ := selLink.Attr("href") 254 | link := Link{ 255 | Title: selLink.Text(), 256 | // FIXME(kazhuravlev): Title contains only title but 257 | // description contains Title + description 258 | Description: selLi.Text(), 259 | URL: url, 260 | } 261 | links = append(links, link) 262 | }) 263 | 264 | // FIXME: In this case we would have an empty category in main index.html with link to 404 page. 265 | if len(links) == 0 { 266 | err = errors.New("category does not contain links") 267 | return false 268 | } 269 | 270 | category = Category{ 271 | Slug: slug.Generate(selCatHeader.Text()), 272 | Title: selCatHeader.Text(), 273 | Description: selDescr.Text(), 274 | Links: links, 275 | } 276 | 277 | return true 278 | }) 279 | 280 | if err != nil { 281 | return nil, fmt.Errorf("build a category: %w", err) 282 | } 283 | 284 | return &category, nil 285 | } 286 | 287 | func rewriteLinksInIndex(doc *goquery.Document, categories map[string]Category) error { 288 | var iterErr error 289 | doc. 290 | Find("body #content ul li ul li a"). 291 | EachWithBreak(func(_ int, s *goquery.Selection) bool { 292 | href, hrefExists := s.Attr("href") 293 | if !hrefExists { 294 | // FIXME: looks like is an error. Tag `a` in our case always 295 | // should have `href` attr. 296 | return true 297 | } 298 | 299 | // do not replace links if no page has been created for it 300 | _, catExists := categories[href] 301 | if !catExists { 302 | return true 303 | } 304 | 305 | linkURL, err := url.Parse(href) 306 | if err != nil { 307 | iterErr = err 308 | return false 309 | } 310 | 311 | if linkURL.Fragment != "" && linkURL.Fragment != "contents" { 312 | s.SetAttr("href", linkURL.Fragment) 313 | } 314 | 315 | return true 316 | }) 317 | 318 | if iterErr != nil { 319 | return iterErr 320 | } 321 | 322 | fmt.Printf("Rewrite links in Index file: %s\n", outIndexFile) 323 | resultHTML, err := doc.Html() 324 | if err != nil { 325 | return fmt.Errorf("render html: %w", err) 326 | } 327 | 328 | if err := os.WriteFile(outIndexFile, []byte(resultHTML), 0644); err != nil { 329 | return fmt.Errorf("rewrite index file: %w", err) 330 | } 331 | 332 | return nil 333 | } 334 | 335 | // renderIndex generate site html (index.html) from markdown file 336 | func renderIndex(srcFilename, outFilename string) error { 337 | input, err := os.ReadFile(srcFilename) 338 | if err != nil { 339 | return err 340 | } 341 | 342 | body, err := markdown.ToHTML(input) 343 | if err != nil { 344 | return err 345 | } 346 | 347 | f, err := os.Create(outFilename) 348 | if err != nil { 349 | return err 350 | } 351 | 352 | fmt.Printf("Write Index file: %s\n", outIndexFile) 353 | data := map[string]interface{}{ 354 | "Body": template2.HTML(body), 355 | } 356 | if err := tpl.Lookup("index.tmpl.html").Execute(f, data); err != nil { 357 | return err 358 | } 359 | 360 | if err := f.Close(); err != nil { 361 | return fmt.Errorf("close index file: %w", err) 362 | } 363 | 364 | return nil 365 | } 366 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "github.com/avelino/awesome-go/pkg/markdown" 6 | "os" 7 | "regexp" 8 | "sort" 9 | "strings" 10 | "testing" 11 | 12 | "github.com/PuerkitoBio/goquery" 13 | ) 14 | 15 | var ( 16 | reContainsLink = regexp.MustCompile(`\* \[.*\]\(.*\)`) 17 | reOnlyLink = regexp.MustCompile(`\* \[.*\]\([^()]*\)$`) 18 | reLinkWithDescription = regexp.MustCompile(`\* \[.*\]\(.*\) - \S.*[\.\!]`) 19 | ) 20 | 21 | func requireNoErr(t *testing.T, err error, msg string) { 22 | // FIXME: replace to github.com/stretchr/testify 23 | t.Helper() 24 | 25 | if msg == "" { 26 | msg = "unknown error" 27 | } 28 | 29 | if err != nil { 30 | t.Fatalf("Received unexpected error [%s]: %+v", msg, err) 31 | } 32 | } 33 | 34 | func goqueryFromReadme(t *testing.T) *goquery.Document { 35 | t.Helper() 36 | 37 | input, err := os.ReadFile(readmePath) 38 | requireNoErr(t, err, "readme file should be exists") 39 | 40 | html, err := markdown.ToHTML(input) 41 | requireNoErr(t, err, "markdown should be rendered to html") 42 | 43 | buf := bytes.NewBuffer(html) 44 | doc, err := goquery.NewDocumentFromReader(buf) 45 | requireNoErr(t, err, "html must be valid for goquery") 46 | 47 | return doc 48 | } 49 | 50 | func TestAlpha(t *testing.T) { 51 | doc := goqueryFromReadme(t) 52 | doc.Find("body > ul").Each(func(i int, s *goquery.Selection) { 53 | if i != 0 { 54 | // skip content menu 55 | // TODO: the sub items (with 3 hash marks `###`) are staying in 56 | // the main list, not respecting the hierarchy and making it 57 | // impossible to test the alphabetical order 58 | testList(t, s) 59 | } 60 | }) 61 | } 62 | 63 | func TestDuplicatedLinks(t *testing.T) { 64 | doc := goqueryFromReadme(t) 65 | links := make(map[string]bool, 0) 66 | doc.Find("body li > a:first-child").Each(func(_ int, s *goquery.Selection) { 67 | t.Run(s.Text(), func(t *testing.T) { 68 | href, ok := s.Attr("href") 69 | if !ok { 70 | t.Error("expected to have href") 71 | } 72 | if links[href] { 73 | t.Fatalf("duplicated link '%s'", href) 74 | } 75 | links[href] = true 76 | }) 77 | }) 78 | } 79 | 80 | // Test if an entry has description, it must be separated from link with ` - ` 81 | func TestSeparator(t *testing.T) { 82 | var matched, containsLink, noDescription bool 83 | input, err := os.ReadFile(readmePath) 84 | requireNoErr(t, err, "readme should be exists") 85 | 86 | lines := strings.Split(string(input), "\n") 87 | for _, line := range lines { 88 | line = strings.Trim(line, " ") 89 | containsLink = reContainsLink.MatchString(line) 90 | if containsLink { 91 | noDescription = reOnlyLink.MatchString(line) 92 | if noDescription { 93 | continue 94 | } 95 | matched = reLinkWithDescription.MatchString(line) 96 | if !matched { 97 | t.Errorf("expected entry to be in form of `* [link] - description.`, got '%s'", line) 98 | } 99 | } 100 | } 101 | } 102 | 103 | func TestRenderIndex(t *testing.T) { 104 | requireNoErr(t, mkdirAll(outDir), "output dir should exists") 105 | 106 | err := renderIndex(readmePath, outIndexFile) 107 | requireNoErr(t, err, "html should be rendered") 108 | } 109 | 110 | func testList(t *testing.T, list *goquery.Selection) { 111 | list.Find("ul").Each(func(_ int, items *goquery.Selection) { 112 | testList(t, items) 113 | items.RemoveFiltered("ul") 114 | }) 115 | t.Run(list.Prev().Text(), func(t *testing.T) { 116 | checkAlphabeticOrder(t, list) 117 | }) 118 | } 119 | 120 | func checkAlphabeticOrder(t *testing.T, s *goquery.Selection) { 121 | items := s.Find("li > a:first-child").Map(func(_ int, li *goquery.Selection) string { 122 | return strings.ToLower(li.Text()) 123 | }) 124 | sorted := make([]string, len(items)) 125 | copy(sorted, items) 126 | sort.Strings(sorted) 127 | for k, item := range items { 128 | if item != sorted[k] { 129 | t.Errorf("expected '%s' but actual is '%s'", sorted[k], item) 130 | } 131 | } 132 | if t.Failed() { 133 | t.Logf("expected order is:\n%s", strings.Join(sorted, "\n")) 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | # Settings in the [build] context are global and are applied to 2 | # all contexts unless otherwise overridden by more specific contexts. 3 | [build] 4 | # Directory to change to before starting a build. 5 | # This is where we will look for package.json/.nvmrc/etc. 6 | # If not set, defaults to the root directory. 7 | base = "./" 8 | 9 | # Directory that contains the deploy-ready HTML files and 10 | # assets generated by the build. This is relative to the base 11 | # directory if one has been set, or the root directory if 12 | # a base has not been set. This sample publishes the directory 13 | # located at the absolute path "root/project/build-output" 14 | 15 | publish = "out/" 16 | 17 | # Default build command. 18 | command = "echo 'default context'" 19 | 20 | [[plugins]] 21 | # Installs the Lighthouse Build Plugin for all deploy contexts 22 | package = "@netlify/plugin-lighthouse" 23 | 24 | # Production context: all deploys from the Production branch 25 | # set in your site’s Branches settings in the UI will inherit 26 | # these settings. You can define environment variables 27 | # here but we recommend using the Netlify UI for sensitive 28 | # values to keep them out of your source repository. 29 | [context.production] 30 | publish = "out/" 31 | 32 | # Redirects and headers are GLOBAL for all builds – they do not 33 | # get scoped to contexts no matter where you define them in the file. 34 | # For context-specific rules, use _headers or _redirects files, 35 | # which are PER-DEPLOY. 36 | 37 | [[redirects]] 38 | from = "/awesome-cloud-native" 39 | to = "/" 40 | status = 302 41 | force = true 42 | 43 | [[redirects]] 44 | from = "/awesome-go-fork" 45 | to = "/" 46 | status = 302 47 | force = true 48 | -------------------------------------------------------------------------------- /pkg/markdown/convert.go: -------------------------------------------------------------------------------- 1 | package markdown 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/avelino/awesome-go/pkg/slug" 7 | "github.com/yuin/goldmark" 8 | "github.com/yuin/goldmark/ast" 9 | "github.com/yuin/goldmark/extension" 10 | "github.com/yuin/goldmark/parser" 11 | "github.com/yuin/goldmark/renderer/html" 12 | "github.com/yuin/goldmark/util" 13 | ) 14 | 15 | // ToHTML converts markdown byte slice to a HTML byte slice 16 | func ToHTML(markdown []byte) ([]byte, error) { 17 | md := goldmark.New( 18 | goldmark.WithExtensions(extension.GFM), 19 | goldmark.WithParserOptions( 20 | parser.WithAutoHeadingID(), // generate heading IDs for content navigation 21 | ), 22 | goldmark.WithRendererOptions( 23 | html.WithXHTML(), 24 | html.WithUnsafe(), // allow inline HTML 25 | ), 26 | ) 27 | 28 | ctx := parser.NewContext( 29 | parser.WithIDs(&IDGenerator{}), // register custom ID generator 30 | ) 31 | 32 | var buf bytes.Buffer 33 | if err := md.Convert(markdown, &buf, parser.WithContext(ctx)); err != nil { 34 | return nil, err 35 | } 36 | 37 | return buf.Bytes(), nil 38 | } 39 | 40 | // IDGenerator for goldmark to provide IDs more similar to GitHub's IDs on markdown parsing 41 | type IDGenerator struct { 42 | used map[string]bool 43 | } 44 | 45 | // Generate an ID 46 | func (g *IDGenerator) Generate(value []byte, _ ast.NodeKind) []byte { 47 | return []byte(slug.Generate(string(value))) 48 | } 49 | 50 | // Put an ID to the list of already used IDs 51 | func (g *IDGenerator) Put(value []byte) { 52 | g.used[util.BytesToReadOnlyString(value)] = true 53 | } 54 | -------------------------------------------------------------------------------- /pkg/markdown/convert_test.go: -------------------------------------------------------------------------------- 1 | package markdown 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func TestToHTML(t *testing.T) { 9 | input := []byte( 10 | `## some headline 11 | followed by some paragraph with [a link](https://example.local) 12 | and some list: 13 | - first 14 | - second 15 | - nested on second level 16 | - nested on third level 17 | - ~~strikethrough~~ 18 | - yet another second level item, **but** with a [a link](https://example.local) 19 | - end 20 | 21 | ### h3 headline/header 22 | 23 | embedded HTML is allowed 24 | `, 25 | ) 26 | expected := []byte( 27 | `

some headline

28 |

followed by some paragraph with a link 29 | and some list:

30 |
    31 |
  • first
  • 32 |
  • second 33 |
      34 |
    • nested on second level 35 |
        36 |
      • nested on third level
      • 37 |
      • strikethrough
      • 38 |
      39 |
    • 40 |
    • yet another second level item, but with a a link
    • 41 |
    42 |
  • 43 |
  • end
  • 44 |
45 |

h3 headline/header

46 |

embedded HTML is allowed

`, 47 | ) 48 | 49 | got, err := ToHTML(input) 50 | if err != nil { 51 | t.Errorf("ToHTML() error = %v", err) 52 | return 53 | } 54 | if strings.TrimSpace(string(got)) != strings.TrimSpace(string(expected)) { 55 | t.Errorf("ToHTML() got = %v, want %v", string(got), string(expected)) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /pkg/slug/generator.go: -------------------------------------------------------------------------------- 1 | package slug 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/avelino/slugify" 7 | ) 8 | 9 | // Generate slugs similar to GitHub's slugs on markdown parsing 10 | func Generate(text string) string { 11 | // FIXME: this is should be like regexp.Replace(`[^-a-zA-Z\d]+`, ``) 12 | s := strings.ReplaceAll(text, "/", "") 13 | return slugify.Slugify(strings.TrimSpace(s)) 14 | } 15 | -------------------------------------------------------------------------------- /pkg/slug/generator_test.go: -------------------------------------------------------------------------------- 1 | package slug 2 | 3 | import "testing" 4 | 5 | func TestGenerate(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | input string 9 | expected string 10 | }{ 11 | { 12 | name: "with spaces", 13 | input: "some string with spaces", 14 | expected: "some-string-with-spaces", 15 | }, 16 | { 17 | name: "with out any non-literal chars", 18 | input: "inputstring", 19 | expected: "inputstring", 20 | }, 21 | { 22 | name: "with whitespace prefix and suffix", 23 | input: " inputstring ", 24 | expected: "inputstring", 25 | }, 26 | { 27 | name: "a mix of special characters", 28 | input: " an input string (with.special/chars,such_as:§\\?$/§&!) ", 29 | expected: "an-input-string-with-specialchars-such-as", 30 | }, 31 | } 32 | for _, tt := range tests { 33 | t.Run(tt.name, func(t *testing.T) { 34 | if got := Generate(tt.input); got != tt.expected { 35 | t.Errorf("Generate() = %v, want %v", got, tt.expected) 36 | } 37 | }) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /stale_repositories_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "fmt" 8 | "log" 9 | "net/http" 10 | "os" 11 | "regexp" 12 | "strings" 13 | "testing" 14 | "text/template" 15 | "time" 16 | 17 | "github.com/PuerkitoBio/goquery" 18 | "golang.org/x/oauth2" 19 | ) 20 | 21 | const issueTemplateContent = ` 22 | {{range .}} 23 | - [ ] {{.}} 24 | {{end}} 25 | ` 26 | 27 | var issueTemplate = template.Must(template.New("issue").Parse(issueTemplateContent)) 28 | 29 | // FIXME: use official github client 30 | var reGithubRepo = regexp.MustCompile("https://github.com/[a-zA-Z0-9-._]+/[a-zA-Z0-9-._]+$") 31 | var githubGETREPO = "https://api.github.com/repos%s" 32 | var githubGETCOMMITS = "https://api.github.com/repos%s/commits" 33 | var githubPOSTISSUES = "https://api.github.com/repos/avelino/awesome-go/issues" 34 | 35 | // FIXME: use https 36 | var awesomeGoGETISSUES = "http://api.github.com/repos/avelino/awesome-go/issues" //only returns open issues 37 | // FIXME: variable has type Duration, but contains a number. we should use 38 | // 39 | // time.Hour * ... or change type of variable 40 | var numberOfYears time.Duration = 1 41 | var timeNow = time.Now() 42 | var issueTitle = fmt.Sprintf("Investigate repositories with more than 1 year without update - %s", timeNow.Format(time.DateOnly)) 43 | 44 | const deadLinkMessage = " this repository might no longer exist! (status code >= 400 returned)" 45 | const movedPermanently = " status code 301 received" 46 | const status302 = " status code 302 received" 47 | const archived = " repository has been archived" 48 | 49 | // LIMIT specifies the max number of repositories that are added in a single run of the script 50 | var LIMIT = 10 51 | var ctr = 0 52 | 53 | type tokenSource struct { 54 | AccessToken string 55 | } 56 | 57 | type issue struct { 58 | Title string `json:"title"` 59 | Body string `json:"body"` 60 | } 61 | 62 | func (t *tokenSource) Token() (*oauth2.Token, error) { 63 | return &oauth2.Token{ 64 | AccessToken: t.AccessToken, 65 | }, nil 66 | } 67 | 68 | func getRepositoriesFromBody(body string) []string { 69 | links := strings.Split(body, "- ") 70 | for i, link := range links { 71 | link = strings.ReplaceAll(link, "\r", "") 72 | link = strings.ReplaceAll(link, "[ ]", "") 73 | link = strings.ReplaceAll(link, "[x]", "") 74 | link = strings.ReplaceAll(link, " ", "") 75 | link = strings.ReplaceAll(link, "\n", "") 76 | link = strings.ReplaceAll(link, deadLinkMessage, "") 77 | link = strings.ReplaceAll(link, movedPermanently, "") 78 | link = strings.ReplaceAll(link, status302, "") 79 | link = strings.ReplaceAll(link, archived, "") 80 | links[i] = link 81 | } 82 | 83 | return links 84 | } 85 | 86 | func generateIssueBody(t *testing.T, repositories []string) (string, error) { 87 | t.Helper() 88 | 89 | buf := bytes.NewBuffer(nil) 90 | err := issueTemplate.Execute(buf, repositories) 91 | requireNoErr(t, err, "Failed to generate template") 92 | 93 | return buf.String(), nil 94 | } 95 | 96 | func createIssue(t *testing.T, staleRepos []string, client *http.Client) { 97 | t.Helper() 98 | 99 | if len(staleRepos) == 0 { 100 | log.Print("NO STALE REPOSITORIES") 101 | return 102 | } 103 | 104 | body, err := generateIssueBody(t, staleRepos) 105 | requireNoErr(t, err, "failed to generate issue body") 106 | 107 | newIssue := &issue{ 108 | Title: issueTitle, 109 | Body: body, 110 | } 111 | buf := bytes.NewBuffer(nil) 112 | requireNoErr(t, json.NewEncoder(buf).Encode(newIssue), "failed to encode json req") 113 | 114 | req, err := http.NewRequest(http.MethodPost, githubPOSTISSUES, buf) 115 | requireNoErr(t, err, "failed to create request") 116 | 117 | _, roundTripErr := client.Do(req) 118 | requireNoErr(t, roundTripErr, "failed to send request") 119 | } 120 | 121 | func getAllFlaggedRepositories(t *testing.T, client *http.Client) map[string]bool { 122 | t.Helper() 123 | 124 | req, err := http.NewRequest(http.MethodGet, awesomeGoGETISSUES, nil) 125 | requireNoErr(t, err, "failed to create request") 126 | 127 | res, err := client.Do(req) 128 | requireNoErr(t, err, "failed to send request") 129 | 130 | defer res.Body.Close() 131 | 132 | var issues []issue 133 | requireNoErr(t, json.NewDecoder(res.Body).Decode(&issues), "failed to unmarshal response") 134 | 135 | addressedRepositories := make(map[string]bool) 136 | for _, issue := range issues { 137 | if issue.Title != issueTitle { 138 | continue 139 | } 140 | 141 | repos := getRepositoriesFromBody(issue.Body) 142 | for _, repo := range repos { 143 | addressedRepositories[repo] = true 144 | } 145 | } 146 | 147 | return addressedRepositories 148 | } 149 | 150 | func checkRepoAvailability(toRun bool, href string, client *http.Client) ([]string, bool) { 151 | if !toRun { 152 | return nil, false 153 | } 154 | 155 | ownerRepo := strings.ReplaceAll(href, "https://github.com", "") 156 | apiCall := fmt.Sprintf(githubGETREPO, ownerRepo) 157 | req, err := http.NewRequest(http.MethodGet, apiCall, nil) 158 | if err != nil { 159 | log.Printf("Failed at repository %s\n", href) 160 | return nil, false 161 | } 162 | 163 | resp, err := client.Do(req) 164 | if err != nil { 165 | log.Printf("Failed at repository %s\n", href) 166 | return nil, false 167 | } 168 | 169 | defer resp.Body.Close() 170 | 171 | var repoResp struct { 172 | Archived bool `json:"archived"` 173 | } 174 | 175 | if err := json.NewDecoder(resp.Body).Decode(&repoResp); err != nil { 176 | return nil, false 177 | } 178 | 179 | var isRepoAdded bool 180 | 181 | var warnings []string 182 | if resp.StatusCode == http.StatusMovedPermanently { 183 | warnings = append(warnings, href+movedPermanently) 184 | log.Printf("%s returned %d", href, resp.StatusCode) 185 | isRepoAdded = true 186 | } 187 | 188 | if resp.StatusCode == http.StatusFound && !isRepoAdded { 189 | warnings = append(warnings, href+status302) 190 | log.Printf("%s returned %d", href, resp.StatusCode) 191 | isRepoAdded = true 192 | } 193 | 194 | if resp.StatusCode >= http.StatusBadRequest && !isRepoAdded { 195 | warnings = append(warnings, href+deadLinkMessage) 196 | log.Printf("%s might not exist!", href) 197 | isRepoAdded = true 198 | } 199 | 200 | if repoResp.Archived && !isRepoAdded { 201 | warnings = append(warnings, href+archived) 202 | log.Printf("%s is archived!", href) 203 | isRepoAdded = true 204 | } 205 | 206 | // FIXME: expression `(len(warnings) > 0) == isRepoAdded` is always true. 207 | return warnings, isRepoAdded 208 | } 209 | 210 | func checkRepoCommitActivity(toRun bool, href string, client *http.Client) ([]string, bool) { 211 | if !toRun { 212 | return nil, false 213 | } 214 | 215 | ownerRepo := strings.ReplaceAll(href, "https://github.com", "") 216 | apiCall := fmt.Sprintf(githubGETCOMMITS, ownerRepo) 217 | req, err := http.NewRequest(http.MethodGet, apiCall, nil) 218 | if err != nil { 219 | log.Printf("Failed at repository %s\n", href) 220 | return nil, false 221 | } 222 | 223 | since := timeNow.Add(-1 * 365 * 24 * numberOfYears * time.Hour) 224 | sinceQuery := since.Format(time.RFC3339) 225 | 226 | q := req.URL.Query() 227 | q.Add("since", sinceQuery) 228 | req.URL.RawQuery = q.Encode() 229 | 230 | resp, err := client.Do(req) 231 | if err != nil { 232 | log.Printf("Failed at repository %s\n", href) 233 | return nil, false 234 | } 235 | 236 | defer resp.Body.Close() 237 | 238 | var respObj []map[string]interface{} 239 | // FIXME: handle error in all that cases 240 | if err := json.NewDecoder(resp.Body).Decode(&respObj); err != nil { 241 | return nil, false 242 | } 243 | 244 | var warnings []string 245 | var isRepoAdded bool 246 | isAged := len(respObj) == 0 247 | if isAged { 248 | log.Printf("%s has not had a commit in a while", href) 249 | warnings = append(warnings, href) 250 | isRepoAdded = true 251 | } 252 | 253 | // FIXME: expression `(len(warnings) > 0) == isRepoAdded` is always true. 254 | return warnings, isRepoAdded 255 | } 256 | 257 | func TestStaleRepository(t *testing.T) { 258 | doc := goqueryFromReadme(t) 259 | 260 | oauth := os.Getenv("OAUTH_TOKEN") 261 | client := &http.Client{ 262 | Transport: &http.Transport{}, 263 | } 264 | 265 | if oauth == "" { 266 | log.Print("No oauth token found. Using unauthenticated client ...") 267 | } else { 268 | tokenSource := &tokenSource{ 269 | AccessToken: oauth, 270 | } 271 | client = oauth2.NewClient(context.Background(), tokenSource) 272 | } 273 | 274 | // FIXME: return addressedRepositories, no need to pass 275 | addressedRepositories := getAllFlaggedRepositories(t, client) 276 | 277 | var staleRepos []string 278 | doc. 279 | Find("body li > a:first-child"). 280 | EachWithBreak(func(_ int, s *goquery.Selection) bool { 281 | href, ok := s.Attr("href") 282 | if !ok { 283 | log.Println("expected to have href") 284 | return true 285 | } 286 | 287 | if ctr >= LIMIT && LIMIT != -1 { 288 | log.Print("Max number of issues created") 289 | return false 290 | } 291 | 292 | if _, issueExists := addressedRepositories[href]; issueExists { 293 | log.Printf("issue already exists for %s\n", href) 294 | return true 295 | } 296 | 297 | if !reGithubRepo.MatchString(href) { 298 | log.Printf("%s non-github repo not currently handled", href) 299 | } 300 | 301 | // FIXME: this is `or` expres24sion. Probably we need `and`? 302 | warnings, isRepoAdded := checkRepoAvailability(true, href, client) 303 | staleRepos = append(staleRepos, warnings...) 304 | 305 | warnings, isRepoAdded = checkRepoCommitActivity(!isRepoAdded, href, client) 306 | staleRepos = append(staleRepos, warnings...) 307 | 308 | if isRepoAdded { 309 | ctr++ 310 | } 311 | 312 | return true 313 | }) 314 | 315 | createIssue(t, staleRepos, client) 316 | } 317 | -------------------------------------------------------------------------------- /tmpl/assets/awesome-go.css: -------------------------------------------------------------------------------- 1 | * { 2 | max-width: 100%; 3 | box-sizing: border-box; 4 | font-family: "Fira Sans"; 5 | text-decoration: none; 6 | font-weight: 300; 7 | } 8 | .awesome-logo { 9 | max-width: 500px; 10 | width: 100%; 11 | margin: auto; 12 | display: block; 13 | } 14 | 15 | a { 16 | color: #669; 17 | } 18 | a:visited, h1, h2, h3, h4 { 19 | color: #494368; 20 | font-weight: 400; 21 | } 22 | h1 > a:nth-child(1) { 23 | margin-left: 10px; 24 | } 25 | h1 > a img { 26 | padding-right: 5px; 27 | } 28 | 29 | #content { 30 | width: 100%; 31 | padding: 40px 80px; 32 | } 33 | 34 | @media (max-width: 720px) { 35 | #content { 36 | padding: 20px 40px; 37 | } 38 | } 39 | @media (max-width: 420px) { 40 | #content * { 41 | word-wrap: break-word; 42 | } 43 | } 44 | 45 | /** ADs 46 | * */ 47 | #ads { 48 | max-width: 330px; 49 | width: 100%; 50 | margin: auto; 51 | margin-top: auto; 52 | margin-right: auto; 53 | margin-bottom: auto; 54 | margin-left: auto; 55 | display: block; 56 | } 57 | 58 | #carbonads { 59 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, 60 | Cantarell, "Helvetica Neue", Helvetica, Arial, sans-serif; 61 | } 62 | 63 | #carbonads { 64 | display: flex; 65 | max-width: 330px; 66 | background-color: hsl(0, 0%, 98%); 67 | box-shadow: 0 1px 4px 1px hsla(0, 0%, 0%, .1); 68 | } 69 | 70 | #carbonads a { 71 | color: inherit; 72 | text-decoration: none; 73 | } 74 | 75 | #carbonads a:hover { 76 | color: inherit; 77 | } 78 | 79 | #carbonads span { 80 | position: relative; 81 | display: block; 82 | overflow: hidden; 83 | } 84 | 85 | #carbonads .carbon-wrap { 86 | display: flex; 87 | } 88 | 89 | .carbon-img { 90 | display: block; 91 | margin: 0; 92 | line-height: 1; 93 | } 94 | 95 | .carbon-img img { 96 | display: block; 97 | } 98 | 99 | .carbon-text { 100 | font-size: 13px; 101 | padding: 10px; 102 | line-height: 1.5; 103 | text-align: left; 104 | } 105 | 106 | .carbon-poweredby { 107 | display: block; 108 | padding: 8px 10px; 109 | background: repeating-linear-gradient(-45deg, transparent, transparent 5px, hsla(0, 0%, 0%, .025) 5px, hsla(0, 0%, 0%, .025) 10px) hsla(203, 11%, 95%, .4); 110 | text-align: center; 111 | text-transform: uppercase; 112 | letter-spacing: .5px; 113 | font-weight: 600; 114 | font-size: 9px; 115 | line-height: 1; 116 | } 117 | 118 | td { 119 | padding: 6px; 120 | } 121 | -------------------------------------------------------------------------------- /tmpl/assets/favicon/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/favicon/android-chrome-192x192.png -------------------------------------------------------------------------------- /tmpl/assets/favicon/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/favicon/android-chrome-512x512.png -------------------------------------------------------------------------------- /tmpl/assets/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /tmpl/assets/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/favicon/favicon.ico -------------------------------------------------------------------------------- /tmpl/assets/favicon/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Awesome Go", 3 | "short_name": "Awesome-Go", 4 | "icons": [ 5 | { 6 | "src": "./android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "./android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasans.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Fira Sans'; 3 | src: local('Fira Sans ExtraLight'), 4 | local('FiraSans-ExtraLight'), 5 | url('/assets/fonts/firasansextralight.woff2') format('woff2'), 6 | url('/assets/fonts/firasansextralight.woff') format('woff'), 7 | url('/assets/fonts/firasansextralight.ttf') format('truetype'); 8 | font-weight: 100; 9 | font-style: normal; 10 | } 11 | @font-face { 12 | font-family: 'Fira Sans'; 13 | src: local('Fira Sans ExtraLight Italic'), 14 | local('FiraSans-ExtraLightItalic'), 15 | url('/assets/fonts/firasansextralightitalic.woff2') format('woff2'), 16 | url('/assets/fonts/firasansextralightitalic.woff') format('woff'), 17 | url('/assets/fonts/firasansextralightitalic.ttf') format('truetype'); 18 | font-weight: 100; 19 | font-style: italic; 20 | } 21 | @font-face { 22 | font-family: 'Fira Sans'; 23 | src: local('Fira Sans Light'), 24 | local('FiraSans-Light'), 25 | url('/assets/fonts/firasanslight.woff2') format('woff2'), 26 | url('/assets/fonts/firasanslight.woff') format('woff'), 27 | url('/assets/fonts/firasanslight.ttf') format('truetype'); 28 | font-weight: 200; 29 | font-style: normal; 30 | } 31 | @font-face { 32 | font-family: 'Fira Sans'; 33 | src: local('Fira Sans Light Italic'), 34 | local('FiraSans-LightItalic'), 35 | url('/assets/fonts/firasanslightitalic.woff2') format('woff2'), 36 | url('/assets/fonts/firasanslightitalic.woff') format('woff'), 37 | url('/assets/fonts/firasanslightitalic.ttf') format('truetype'); 38 | font-weight: 200; 39 | font-style: italic; 40 | } 41 | @font-face { 42 | font-family: 'Fira Sans'; 43 | src: local('Fira Sans Book'), 44 | local('FiraSans-Book'), 45 | url('/assets/fonts/firasansbook.woff2') format('woff2'), 46 | url('/assets/fonts/firasansbook.woff') format('woff'), 47 | url('/assets/fonts/firasansbook.ttf') format('truetype'); 48 | font-weight: 300; 49 | font-style: normal; 50 | } 51 | @font-face { 52 | font-family: 'Fira Sans'; 53 | src: local('Fira Sans Book Italic'), 54 | local('FiraSans-BookItalic'), 55 | url('/assets/fonts/firasansbookitalic.woff2') format('woff2'), 56 | url('/assets/fonts/firasansbookitalic.woff') format('woff'), 57 | url('/assets/fonts/firasansbookitalic.ttf') format('truetype'); 58 | font-weight: 300; 59 | font-style: italic; 60 | } 61 | @font-face { 62 | font-family: 'Fira Sans'; 63 | src: local('Fira Sans'), 64 | local('FiraSans-Regular'), 65 | url('/assets/fonts/firasans.woff2') format('woff2'), 66 | url('/assets/fonts/firasans.woff') format('woff'), 67 | url('/assets/fonts/firasans.ttf') format('truetype'); 68 | font-weight: 400; 69 | font-style: normal; 70 | } 71 | @font-face { 72 | font-family: 'Fira Sans'; 73 | src: local('Fira Sans Italic'), 74 | local('FiraSans-Italic'), 75 | url('/assets/fonts/firasansitalic.woff2') format('woff2'), 76 | url('/assets/fonts/firasansitalic.woff') format('woff'), 77 | url('/assets/fonts/firasansitalic.ttf') format('truetype'); 78 | font-weight: 400; 79 | font-style: italic; 80 | } 81 | @font-face { 82 | font-family: 'Fira Sans'; 83 | src: local('Fira Sans Medium'), 84 | local('FiraSans-Medium'), 85 | url('/assets/fonts/firasansmedium.woff2') format('woff2'), 86 | url('/assets/fonts/firasansmedium.woff') format('woff'), 87 | url('/assets/fonts/firasansmedium.ttf') format('truetype'); 88 | font-weight: 500; 89 | font-style: normal; 90 | } 91 | @font-face { 92 | font-family: 'Fira Sans'; 93 | src: local('Fira Sans Medium Italic'), 94 | local('FiraSans-MediumItalic'), 95 | url('/assets/fonts/firasansmediumitalic.woff2') format('woff2'), 96 | url('/assets/fonts/firasansmediumitalic.woff') format('woff'), 97 | url('/assets/fonts/firasansmediumitalic.ttf') format('truetype'); 98 | font-weight: 500; 99 | font-style: italic; 100 | } 101 | @font-face { 102 | font-family: 'Fira Sans'; 103 | src: local('Fira Sans SemiBold'), 104 | local('FiraSans-SemiBold'), 105 | url('/assets/fonts/firasanssemibold.woff2') format('woff2'), 106 | url('/assets/fonts/firasanssemibold.woff') format('woff'), 107 | url('/assets/fonts/firasanssemibold.ttf') format('truetype'); 108 | font-weight: 600; 109 | font-style: normal; 110 | } 111 | @font-face { 112 | font-family: 'Fira Sans'; 113 | src: local('Fira Sans SemiBold Italic'), 114 | local('FiraSans-SemiBoldItalic'), 115 | url('/assets/fonts/firasanssemibolditalic.woff2') format('woff2'), 116 | url('/assets/fonts/firasanssemibolditalic.woff') format('woff'), 117 | url('/assets/fonts/firasanssemibolditalic.ttf') format('truetype'); 118 | font-weight: 600; 119 | font-style: italic; 120 | } 121 | @font-face { 122 | font-family: 'Fira Sans'; 123 | src: local('Fira Sans Bold'), 124 | local('FiraSans-Bold'), 125 | url('/assets/fonts/firasansbold.woff2') format('woff2'), 126 | url('/assets/fonts/firasansbold.woff') format('woff'), 127 | url('/assets/fonts/firasansbold.ttf') format('truetype'); 128 | font-weight: 700; 129 | font-style: normal; 130 | } 131 | @font-face { 132 | font-family: 'Fira Sans'; 133 | src: local('Fira Sans Bold Italic'), 134 | local('FiraSans-BoldItalic'), 135 | url('/assets/fonts/firasansbolditalic.woff2') format('woff2'), 136 | url('/assets/fonts/firasansbolditalic.woff') format('woff'), 137 | url('/assets/fonts/firasansbolditalic.ttf') format('truetype'); 138 | font-weight: 700; 139 | font-style: italic; 140 | } 141 | -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasans.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasans.ttf -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasans.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasans.woff -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasans.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasans.woff2 -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasansbold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasansbold.ttf -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasansbold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasansbold.woff -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasansbold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasansbold.woff2 -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasansbolditalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasansbolditalic.ttf -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasansbolditalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasansbolditalic.woff -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasansbolditalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasansbolditalic.woff2 -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasansbook.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasansbook.ttf -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasansbook.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasansbook.woff -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasansbook.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasansbook.woff2 -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasansbookitalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasansbookitalic.ttf -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasansbookitalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasansbookitalic.woff -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasansbookitalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasansbookitalic.woff2 -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasansextralight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasansextralight.ttf -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasansextralight.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasansextralight.woff -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasansextralight.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasansextralight.woff2 -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasansextralightitalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasansextralightitalic.ttf -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasansextralightitalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasansextralightitalic.woff -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasansextralightitalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasansextralightitalic.woff2 -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasansitalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasansitalic.ttf -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasansitalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasansitalic.woff -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasansitalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasansitalic.woff2 -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasanslight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasanslight.ttf -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasanslight.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasanslight.woff -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasanslight.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasanslight.woff2 -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasanslightitalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasanslightitalic.ttf -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasanslightitalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasanslightitalic.woff -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasanslightitalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasanslightitalic.woff2 -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasansmedium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasansmedium.ttf -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasansmedium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasansmedium.woff -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasansmedium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasansmedium.woff2 -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasansmediumitalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasansmediumitalic.ttf -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasansmediumitalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasansmediumitalic.woff -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasansmediumitalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasansmediumitalic.woff2 -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasanssemibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasanssemibold.ttf -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasanssemibold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasanssemibold.woff -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasanssemibold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasanssemibold.woff2 -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasanssemibolditalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasanssemibolditalic.ttf -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasanssemibolditalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasanssemibolditalic.woff -------------------------------------------------------------------------------- /tmpl/assets/fonts/firasanssemibolditalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/fonts/firasanssemibolditalic.woff2 -------------------------------------------------------------------------------- /tmpl/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/logo.png -------------------------------------------------------------------------------- /tmpl/assets/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v3.0.1 | MIT License | git.io/normalize */ 2 | 3 | /** 4 | * 1. Set default font family to sans-serif. 5 | * 2. Prevent iOS text size adjust after orientation change, without disabling 6 | * user zoom. 7 | */ 8 | 9 | html { 10 | font-family: sans-serif; /* 1 */ 11 | -ms-text-size-adjust: 100%; /* 2 */ 12 | -webkit-text-size-adjust: 100%; /* 2 */ 13 | } 14 | 15 | /** 16 | * Remove default margin. 17 | */ 18 | 19 | body { 20 | margin: 0; 21 | } 22 | 23 | /* HTML5 display definitions 24 | ========================================================================== */ 25 | 26 | /** 27 | * Correct `block` display not defined for any HTML5 element in IE 8/9. 28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11 and Firefox. 29 | * Correct `block` display not defined for `main` in IE 11. 30 | */ 31 | 32 | article, 33 | aside, 34 | details, 35 | figcaption, 36 | figure, 37 | footer, 38 | header, 39 | hgroup, 40 | main, 41 | nav, 42 | section, 43 | summary { 44 | display: block; 45 | } 46 | 47 | /** 48 | * 1. Correct `inline-block` display not defined in IE 8/9. 49 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. 50 | */ 51 | 52 | audio, 53 | canvas, 54 | progress, 55 | video { 56 | display: inline-block; /* 1 */ 57 | vertical-align: baseline; /* 2 */ 58 | } 59 | 60 | /** 61 | * Prevent modern browsers from displaying `audio` without controls. 62 | * Remove excess height in iOS 5 devices. 63 | */ 64 | 65 | audio:not([controls]) { 66 | display: none; 67 | height: 0; 68 | } 69 | 70 | /** 71 | * Address `[hidden]` styling not present in IE 8/9/10. 72 | * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. 73 | */ 74 | 75 | [hidden], 76 | template { 77 | display: none; 78 | } 79 | 80 | /* Links 81 | ========================================================================== */ 82 | 83 | /** 84 | * Remove the gray background color from active links in IE 10. 85 | */ 86 | 87 | a { 88 | background: transparent; 89 | } 90 | 91 | /** 92 | * Improve readability when focused and also mouse hovered in all browsers. 93 | */ 94 | 95 | a:active, 96 | a:hover { 97 | outline: 0; 98 | } 99 | 100 | /* Text-level semantics 101 | ========================================================================== */ 102 | 103 | /** 104 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome. 105 | */ 106 | 107 | abbr[title] { 108 | border-bottom: 1px dotted; 109 | } 110 | 111 | /** 112 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. 113 | */ 114 | 115 | b, 116 | strong { 117 | font-weight: bold; 118 | } 119 | 120 | /** 121 | * Address styling not present in Safari and Chrome. 122 | */ 123 | 124 | dfn { 125 | font-style: italic; 126 | } 127 | 128 | /** 129 | * Address variable `h1` font-size and margin within `section` and `article` 130 | * contexts in Firefox 4+, Safari, and Chrome. 131 | */ 132 | 133 | h1 { 134 | font-size: 2em; 135 | margin: 0.67em 0; 136 | } 137 | 138 | /** 139 | * Address styling not present in IE 8/9. 140 | */ 141 | 142 | mark { 143 | background: #ff0; 144 | color: #000; 145 | } 146 | 147 | /** 148 | * Address inconsistent and variable font size in all browsers. 149 | */ 150 | 151 | small { 152 | font-size: 80%; 153 | } 154 | 155 | /** 156 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 157 | */ 158 | 159 | sub, 160 | sup { 161 | font-size: 75%; 162 | line-height: 0; 163 | position: relative; 164 | vertical-align: baseline; 165 | } 166 | 167 | sup { 168 | top: -0.5em; 169 | } 170 | 171 | sub { 172 | bottom: -0.25em; 173 | } 174 | 175 | /* Embedded content 176 | ========================================================================== */ 177 | 178 | /** 179 | * Remove border when inside `a` element in IE 8/9/10. 180 | */ 181 | 182 | img { 183 | border: 0; 184 | } 185 | 186 | /** 187 | * Correct overflow not hidden in IE 9/10/11. 188 | */ 189 | 190 | svg:not(:root) { 191 | overflow: hidden; 192 | } 193 | 194 | /* Grouping content 195 | ========================================================================== */ 196 | 197 | /** 198 | * Address margin not present in IE 8/9 and Safari. 199 | */ 200 | 201 | figure { 202 | margin: 1em 40px; 203 | } 204 | 205 | /** 206 | * Address differences between Firefox and other browsers. 207 | */ 208 | 209 | hr { 210 | -moz-box-sizing: content-box; 211 | box-sizing: content-box; 212 | height: 0; 213 | } 214 | 215 | /** 216 | * Contain overflow in all browsers. 217 | */ 218 | 219 | pre { 220 | overflow: auto; 221 | } 222 | 223 | /** 224 | * Address odd `em`-unit font size rendering in all browsers. 225 | */ 226 | 227 | code, 228 | kbd, 229 | pre, 230 | samp { 231 | font-family: monospace, monospace; 232 | font-size: 1em; 233 | } 234 | 235 | /* Forms 236 | ========================================================================== */ 237 | 238 | /** 239 | * Known limitation: by default, Chrome and Safari on OS X allow very limited 240 | * styling of `select`, unless a `border` property is set. 241 | */ 242 | 243 | /** 244 | * 1. Correct color not being inherited. 245 | * Known issue: affects color of disabled elements. 246 | * 2. Correct font properties not being inherited. 247 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. 248 | */ 249 | 250 | button, 251 | input, 252 | optgroup, 253 | select, 254 | textarea { 255 | color: inherit; /* 1 */ 256 | font: inherit; /* 2 */ 257 | margin: 0; /* 3 */ 258 | } 259 | 260 | /** 261 | * Address `overflow` set to `hidden` in IE 8/9/10/11. 262 | */ 263 | 264 | button { 265 | overflow: visible; 266 | } 267 | 268 | /** 269 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 270 | * All other form control elements do not inherit `text-transform` values. 271 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. 272 | * Correct `select` style inheritance in Firefox. 273 | */ 274 | 275 | button, 276 | select { 277 | text-transform: none; 278 | } 279 | 280 | /** 281 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 282 | * and `video` controls. 283 | * 2. Correct inability to style clickable `input` types in iOS. 284 | * 3. Improve usability and consistency of cursor style between image-type 285 | * `input` and others. 286 | */ 287 | 288 | button, 289 | html input[type="button"], /* 1 */ 290 | input[type="reset"], 291 | input[type="submit"] { 292 | -webkit-appearance: button; /* 2 */ 293 | cursor: pointer; /* 3 */ 294 | } 295 | 296 | /** 297 | * Re-set default cursor for disabled elements. 298 | */ 299 | 300 | button[disabled], 301 | html input[disabled] { 302 | cursor: default; 303 | } 304 | 305 | /** 306 | * Remove inner padding and border in Firefox 4+. 307 | */ 308 | 309 | button::-moz-focus-inner, 310 | input::-moz-focus-inner { 311 | border: 0; 312 | padding: 0; 313 | } 314 | 315 | /** 316 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 317 | * the UA stylesheet. 318 | */ 319 | 320 | input { 321 | line-height: normal; 322 | } 323 | 324 | /** 325 | * It's recommended that you don't attempt to style these elements. 326 | * Firefox's implementation doesn't respect box-sizing, padding, or width. 327 | * 328 | * 1. Address box sizing set to `content-box` in IE 8/9/10. 329 | * 2. Remove excess padding in IE 8/9/10. 330 | */ 331 | 332 | input[type="checkbox"], 333 | input[type="radio"] { 334 | box-sizing: border-box; /* 1 */ 335 | padding: 0; /* 2 */ 336 | } 337 | 338 | /** 339 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain 340 | * `font-size` values of the `input`, it causes the cursor style of the 341 | * decrement button to change from `default` to `text`. 342 | */ 343 | 344 | input[type="number"]::-webkit-inner-spin-button, 345 | input[type="number"]::-webkit-outer-spin-button { 346 | height: auto; 347 | } 348 | 349 | /** 350 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome. 351 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome 352 | * (include `-moz` to future-proof). 353 | */ 354 | 355 | input[type="search"] { 356 | -webkit-appearance: textfield; /* 1 */ 357 | -moz-box-sizing: content-box; 358 | -webkit-box-sizing: content-box; /* 2 */ 359 | box-sizing: content-box; 360 | } 361 | 362 | /** 363 | * Remove inner padding and search cancel button in Safari and Chrome on OS X. 364 | * Safari (but not Chrome) clips the cancel button when the search input has 365 | * padding (and `textfield` appearance). 366 | */ 367 | 368 | input[type="search"]::-webkit-search-cancel-button, 369 | input[type="search"]::-webkit-search-decoration { 370 | -webkit-appearance: none; 371 | } 372 | 373 | /** 374 | * Define consistent border, margin, and padding. 375 | */ 376 | 377 | fieldset { 378 | border: 1px solid #c0c0c0; 379 | margin: 0 2px; 380 | padding: 0.35em 0.625em 0.75em; 381 | } 382 | 383 | /** 384 | * 1. Correct `color` not being inherited in IE 8/9/10/11. 385 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 386 | */ 387 | 388 | legend { 389 | border: 0; /* 1 */ 390 | padding: 0; /* 2 */ 391 | } 392 | 393 | /** 394 | * Remove default vertical scrollbar in IE 8/9/10/11. 395 | */ 396 | 397 | textarea { 398 | overflow: auto; 399 | } 400 | 401 | /** 402 | * Don't inherit the `font-weight` (applied by a rule above). 403 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. 404 | */ 405 | 406 | optgroup { 407 | font-weight: bold; 408 | } 409 | 410 | /* Tables 411 | ========================================================================== */ 412 | 413 | /** 414 | * Remove most spacing between table cells. 415 | */ 416 | 417 | table { 418 | border-collapse: collapse; 419 | border-spacing: 0; 420 | } 421 | 422 | td, 423 | th { 424 | padding: 0; 425 | } 426 | -------------------------------------------------------------------------------- /tmpl/assets/sponsors/doppler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avelino/awesome-go/a67740d790f9ed5ef2df5cce77aa4346fec660b5/tmpl/assets/sponsors/doppler.png -------------------------------------------------------------------------------- /tmpl/category-index.tmpl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{.Title}} - Awesome Go / Golang 8 | 10 | 12 | 13 | 15 | 16 | 18 | 20 | 22 | 23 | 25 | 27 | 29 | 31 | 33 | 34 | 35 | 36 |
37 |
38 |

awesome-go {{.Title}} - 42 | Awesome Go

43 |

{{.Description}}

44 |

Build Status Awesome Slack Widget Netlify Status 64

65 |

70 |
🗺️ back to content 72 | menu 73 | Deploys by Netlify 84 |
85 | 90 | 95 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /tmpl/index.tmpl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | A curated list of awesome Go frameworks, libraries and software - Awesome Go / Golang 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 |
34 |
35 | 36 |
37 | 38 | {{.Body}} 39 | 40 | 41 | Deploys by Netlify 42 | 43 |
44 | 45 | 46 | 47 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /tmpl/robots.txt: -------------------------------------------------------------------------------- 1 | User-Agent: * 2 | Allow: / 3 | -------------------------------------------------------------------------------- /tmpl/sitemap.tmpl.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | https://www.awesome-go.com/ 10 | 2016-10-10T07:39:03+00:00 11 | 12 | {{range .}} 13 | 14 | https://www.awesome-go.com/{{.Slug}} 15 | 2016-10-10T07:39:03+00:00 16 | 17 | {{end}} 18 | 19 | --------------------------------------------------------------------------------