{"id":13723988,"url":"https://github.com/vektah/goparsify","last_synced_at":"2025-09-19T01:12:18.076Z","repository":{"id":57550908,"uuid":"99694331","full_name":"vektah/goparsify","owner":"vektah","description":"golang parser-combinator library","archived":false,"fork":false,"pushed_at":"2022-10-14T01:04:55.000Z","size":94,"stargazers_count":76,"open_issues_count":4,"forks_count":16,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-09-09T06:38:33.796Z","etag":null,"topics":["ast","golang","lexer","parse","parser-combinators"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vektah.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-08-08T13:12:50.000Z","updated_at":"2025-06-13T00:04:22.000Z","dependencies_parsed_at":"2023-01-19T22:52:52.686Z","dependency_job_id":null,"html_url":"https://github.com/vektah/goparsify","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/vektah/goparsify","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vektah%2Fgoparsify","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vektah%2Fgoparsify/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vektah%2Fgoparsify/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vektah%2Fgoparsify/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vektah","download_url":"https://codeload.github.com/vektah/goparsify/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vektah%2Fgoparsify/sbom","scorecard":{"id":918171,"data":{"date":"2025-08-11","repo":{"name":"github.com/vektah/goparsify","commit":"3e20a3b9244aa003fcaf26a625ef48246aef55f7"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Code-Review","score":0,"reason":"Found 1/28 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: licence.md:0","Info: FSF or OSI recognized license: MIT License: licence.md:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 3 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-24T22:27:57.844Z","repository_id":57550908,"created_at":"2025-08-24T22:27:57.844Z","updated_at":"2025-08-24T22:27:57.844Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":275862877,"owners_count":25541903,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-18T02:00:09.552Z","response_time":77,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ast","golang","lexer","parse","parser-combinators"],"created_at":"2024-08-03T01:01:48.115Z","updated_at":"2025-09-19T01:12:18.043Z","avatar_url":"https://github.com/vektah.png","language":"Go","funding_links":[],"categories":["Go"],"sub_categories":[],"readme":"goparsify [![CircleCI](https://circleci.com/gh/Vektah/goparsify/tree/master.svg?style=shield)](https://circleci.com/gh/Vektah/goparsify/tree/master) [![godoc](http://b.repl.ca/v1/godoc-reference-blue.png)](https://godoc.org/github.com/Vektah/goparsify) [![Go Report Card](https://goreportcard.com/badge/github.com/vektah/goparsify)](https://goreportcard.com/report/github.com/vektah/goparsify)\n=========\n\nA parser-combinator library for building easy to test, read and maintain parsers using functional composition.\n\nEverything should be unicode safe by default, but you can opt out of unicode whitespace for a decent ~20% performance boost.\n```go\nRun(parser, input, ASCIIWhitespace)\n```\n\n### benchmarks\nI dont have many benchmarks set up yet, its pretty quick:\n```\n$ go test -benchmem -bench=. ./json\nBenchmarkUnmarshalParsec-8         20000             74880 ns/op           50846 B/op       1318 allocs/op\nBenchmarkUnmarshalParsify-8        30000             50631 ns/op           45055 B/op        233 allocs/op\nBenchmarkUnmarshalStdlib-8         30000             46989 ns/op           14210 B/op        260 allocs/op\nPASS\nok      github.com/vektah/goparsify/json        6.124s\n```\n\nMost of the remaining small allocs are from putting things in `interface{}` and are pretty unavoidable. https://www.darkcoding.net/software/go-the-price-of-interface/ is a good read.\n\n### debugging parsers\n\nWhen a parser isnt working as you intended you can build with debugging and enable logging to get a detailed log of exactly what the parser is doing.\n\n1. First build with debug using `-tags debug`\n2. enable logging by calling `EnableLogging(os.Stdout)` in your code\n\nThis works great with tests, eg in the goparsify source tree\n```\nadam:goparsify(master)$ go test -tags debug ./html -v\n=== RUN   TestParse\nhtml.go:48 | \u003cbody\u003ehello \u003cp  | tag {\nhtml.go:43 | \u003cbody\u003ehello \u003cp  |   tstart {\nhtml.go:43 | body\u003ehello \u003cp c |     \u003c found \u003c\nhtml.go:20 | \u003ehello \u003cp color |     identifier found body\nhtml.go:33 | \u003ehello \u003cp color |     attrs {\nhtml.go:32 | \u003ehello \u003cp color |       attr {\nhtml.go:20 | \u003ehello \u003cp color |         identifier did not find [a-zA-Z][a-zA-Z0-9]*\nhtml.go:32 | \u003ehello \u003cp color |       } did not find [a-zA-Z][a-zA-Z0-9]*\nhtml.go:33 | \u003ehello \u003cp color |     } found\nhtml.go:43 | hello \u003cp color= |     \u003e found \u003e\nhtml.go:43 | hello \u003cp color= |   } found [\u003c,body,,map[string]string{},\u003e]\nhtml.go:24 | hello \u003cp color= |   elements {\nhtml.go:23 | hello \u003cp color= |     element {\nhtml.go:21 | \u003cp color=\"blue\" |       text found hello\nhtml.go:23 | \u003cp color=\"blue\" |     } found \"hello \"\nhtml.go:23 | \u003cp color=\"blue\" |     element {\nhtml.go:21 | \u003cp color=\"blue\" |       text did not find \u003c\u003e\nhtml.go:48 | \u003cp color=\"blue\" |       tag {\nhtml.go:43 | \u003cp color=\"blue\" |         tstart {\nhtml.go:43 | p color=\"blue\"\u003e |           \u003c found \u003c\nhtml.go:20 |  color=\"blue\"\u003ew |           identifier found p\nhtml.go:33 |  color=\"blue\"\u003ew |           attrs {\nhtml.go:32 |  color=\"blue\"\u003ew |             attr {\nhtml.go:20 | =\"blue\"\u003eworld\u003c/ |               identifier found color\nhtml.go:32 | \"blue\"\u003eworld\u003c/p |               = found =\nhtml.go:32 | \u003eworld\u003c/p\u003e\u003c/bod |               string literal found \"blue\"\nhtml.go:32 | \u003eworld\u003c/p\u003e\u003c/bod |             } found [color,=,\"blue\"]\nhtml.go:32 | \u003eworld\u003c/p\u003e\u003c/bod |             attr {\nhtml.go:20 | \u003eworld\u003c/p\u003e\u003c/bod |               identifier did not find [a-zA-Z][a-zA-Z0-9]*\nhtml.go:32 | \u003eworld\u003c/p\u003e\u003c/bod |             } did not find [a-zA-Z][a-zA-Z0-9]*\nhtml.go:33 | \u003eworld\u003c/p\u003e\u003c/bod |           } found [[color,=,\"blue\"]]\nhtml.go:43 | world\u003c/p\u003e\u003c/body |           \u003e found \u003e\nhtml.go:43 | world\u003c/p\u003e\u003c/body |         } found [\u003c,p,,map[string]string{\"color\":\"blue\"},\u003e]\nhtml.go:24 | world\u003c/p\u003e\u003c/body |         elements {\nhtml.go:23 | world\u003c/p\u003e\u003c/body |           element {\nhtml.go:21 | \u003c/p\u003e\u003c/body\u003e     |             text found world\nhtml.go:23 | \u003c/p\u003e\u003c/body\u003e     |           } found \"world\"\nhtml.go:23 | \u003c/p\u003e\u003c/body\u003e     |           element {\nhtml.go:21 | \u003c/p\u003e\u003c/body\u003e     |             text did not find \u003c\u003e\nhtml.go:48 | \u003c/p\u003e\u003c/body\u003e     |             tag {\nhtml.go:43 | \u003c/p\u003e\u003c/body\u003e     |               tstart {\nhtml.go:43 | /p\u003e\u003c/body\u003e      |                 \u003c found \u003c\nhtml.go:20 | /p\u003e\u003c/body\u003e      |                 identifier did not find [a-zA-Z][a-zA-Z0-9]*\nhtml.go:43 | \u003c/p\u003e\u003c/body\u003e     |               } did not find [a-zA-Z][a-zA-Z0-9]*\nhtml.go:48 | \u003c/p\u003e\u003c/body\u003e     |             } did not find [a-zA-Z][a-zA-Z0-9]*\nhtml.go:23 | \u003c/p\u003e\u003c/body\u003e     |           } did not find [a-zA-Z][a-zA-Z0-9]*\nhtml.go:24 | \u003c/p\u003e\u003c/body\u003e     |         } found [\"world\"]\nhtml.go:44 | \u003c/p\u003e\u003c/body\u003e     |         tend {\nhtml.go:44 | p\u003e\u003c/body\u003e       |           \u003c/ found \u003c/\nhtml.go:20 | \u003e\u003c/body\u003e        |           identifier found p\nhtml.go:44 | \u003c/body\u003e         |           \u003e found \u003e\nhtml.go:44 | \u003c/body\u003e         |         } found [\u003c/,,p,\u003e]\nhtml.go:48 | \u003c/body\u003e         |       } found \"hello \"\nhtml.go:23 | \u003c/body\u003e         |     } found html.htmlTag{Name:\"p\", Attributes:map[string]string{\"color\":\"blue\"}, Body:[]interface {}{\"world\"}}\nhtml.go:23 | \u003c/body\u003e         |     element {\nhtml.go:48 | \u003c/body\u003e         |       tag {\nhtml.go:43 | \u003c/body\u003e         |         tstart {\nhtml.go:43 | /body\u003e          |           \u003c found \u003c\nhtml.go:20 | /body\u003e          |           identifier did not find [a-zA-Z][a-zA-Z0-9]*\nhtml.go:43 | \u003c/body\u003e         |         } did not find [a-zA-Z][a-zA-Z0-9]*\nhtml.go:48 | \u003c/body\u003e         |       } did not find [a-zA-Z][a-zA-Z0-9]*\nhtml.go:21 | \u003c/body\u003e         |       text did not find \u003c\u003e\nhtml.go:23 | \u003c/body\u003e         |     } did not find [a-zA-Z][a-zA-Z0-9]*\nhtml.go:24 | \u003c/body\u003e         |   } found [\"hello \",html.htmlTag{Name:\"p\", Attributes:map[string]string{\"color\":\"blue\"}, Body:[]interface {}{\"world\"}}]\nhtml.go:44 | \u003c/body\u003e         |   tend {\nhtml.go:44 | body\u003e           |     \u003c/ found \u003c/\nhtml.go:20 | \u003e               |     identifier found body\nhtml.go:44 |                 |     \u003e found \u003e\nhtml.go:44 |                 |   } found [\u003c/,,body,\u003e]\nhtml.go:48 |                 | } found [[\u003c,body,,map[string]string{},\u003e],,[]interface {}{\"hello \", html.htmlTag{Name:\"p\", Attributes:map[string]string{\"color\":\"blue\"}, Body:[]interface {}{\"world\"}}},[\u003c/,,body,\u003e]]\n--- PASS: TestParse (0.00s)\nPASS\nok      github.com/vektah/goparsify/html        0.117s\n```\n\n### debugging performance\nIf you build the parser with -tags debug it will instrument each parser and a call to DumpDebugStats() will show stats:\n\n|             var name |              matches |      total time |       self time |      calls |     errors | location\n| -------------------- | -------------------- | --------------- | --------------- | ---------- | ---------- | ----------\n|               _value |                Any() |      5.0685431s |       34.0131ms |     878801 |          0 | json.go:36\n|              _object |                Seq() |      3.7513821s |       10.5038ms |     161616 |      40403 | json.go:24\n|          _properties |               Some() |      3.6863512s |        5.5028ms |     121213 |          0 | json.go:14\n|          _properties |                Seq() |      3.4912614s |       46.0229ms |     818185 |          0 | json.go:14\n|               _array |                Seq() |      931.4679ms |        3.5014ms |      65660 |      55558 | json.go:16\n|               _array |               Some() |      911.4597ms |              0s |      10102 |          0 | json.go:16\n|          _properties |       string literal |      126.0662ms |       44.5201ms |     818185 |          0 | json.go:14\n|              _string |       string literal |        67.033ms |       26.0126ms |     671723 |     136369 | json.go:12\n|          _properties |                    : |       50.0238ms |       45.0205ms |     818185 |          0 | json.go:14\n|          _properties |                    , |       48.5189ms |       36.0146ms |     818185 |     121213 | json.go:14\n|              _number |       number literal |       28.5159ms |       10.5062ms |     287886 |     106066 | json.go:13\n|                _true |                 true |       17.5086ms |       12.5069ms |     252537 |     232332 | json.go:10\n|                _null |                 null |       14.5082ms |        11.007ms |     252538 |     252535 | json.go:9\n|              _object |                    } |       10.5051ms |       10.5033ms |     121213 |          0 | json.go:24\n|               _false |                false |       10.5049ms |        5.0019ms |     232333 |     222229 | json.go:11\n|              _object |                    { |       10.0046ms |        5.0052ms |     161616 |      40403 | json.go:24\n|               _array |                    , |        4.5024ms |        4.0018ms |      50509 |      10102 | json.go:16\n|               _array |                    [ |        4.5014ms |        2.0006ms |      65660 |      55558 | json.go:16\n|               _array |                    ] |              0s |              0s |      10102 |          0 | json.go:16\n\nAll times are cumulative, it would be nice to break this down into a parse tree with relative times. This is a nice addition to pprof as it will break down the parsers based on where they are used instead of grouping them all by type. \n\nThis is **free** when the debug tag isnt used.  \n\n### example calculator\nLets say we wanted to build a calculator that could take an expression and calculate the result.\n\nLets start with test:\n```go\nfunc TestNumbers(t *testing.T) {\n\tresult, err := Calc(`1`)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, 1, result)\n}\n```\n\nThen define a parser for numbers\n```go\nvar number = NumberLit().Map(func(n Result) Result {\n    switch i := n.Result.(type) {\n    case int64:\n        return Result{Result: float64(i)}\n    case float64:\n        return Result{Result: i}\n    default:\n        panic(fmt.Errorf(\"unknown value %#v\", i))\n    }\n})\n\nfunc Calc(input string) (float64, error) {\n\tresult, err := Run(y, input)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn result.(float64), nil\n}\n\n```\n\nThis parser will return numbers either as float64 or int depending on the literal, for this calculator we only want floats so we Map the results and type cast.\n\nRun the tests and make sure everything is ok.\n\nTime to add addition\n\n```go\nfunc TestAddition(t *testing.T) {\n\tresult, err := Calc(`1+1`)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, 2, result)\n}\n\n\nvar sumOp  = Chars(\"+-\", 1, 1)\n\nsum = Seq(number, Some(And(sumOp, number))).Map(func(n Result) Result {\n    i := n.Child[0].Result.(float64)\n\n    for _, op := range n.Child[1].Child {\n        switch op.Child[0].Token {\n        case \"+\":\n            i += op.Child[1].Result.(float64)\n        case \"-\":\n            i -= op.Child[1].Result.(float64)\n        }\n    }\n\n    return Result{Result: i}\n})\n\n// and update Calc to point to the new root parser -\u003e `result, err := ParseString(sum, input)`\n```\n\nThis parser will match number ([+-] number)+, then map its to be the sum. See how the Child map directly to the positions in the parsers? n is the result of the and, `n.Child[0]` is its first argument, `n.Child[1]` is the result of the Some parser, `n.Child[1].Child[0]` is the result of the first And and so fourth. Given how closely tied the parser and the Map are it is good to keep the two together.\n\nYou can continue like this and add multiplication and parenthesis fairly easily. Eventually if you keep adding parsers you will end up with a loop, and go will give you a handy error message like:\n```\ntypechecking loop involving value = goparsify.Any(number, groupExpr)\n```\n\nwe need to break the loop using a pointer, then set its value in init\n```go\nvar (\n    value Parser\n    prod = Seq(\u0026value, Some(And(prodOp, \u0026value)))\n)\n\nfunc init() {\n\tvalue = Any(number, groupExpr)\n}\n```\n\nTake a look at [calc](calc/calc.go) for a full example.\n\n### preventing backtracking with cuts\nA cut is a marker that prevents backtracking past the point it was set. This greatly improves error messages when used correctly: \n```go\nalpha := Chars(\"a-z\")\n\n// without a cut if the close tag is left out the parser will backtrack and ignore the rest of the string\nnocut := Many(Any(Seq(\"\u003c\", alpha, \"\u003e\"), alpha))\n_, err := Run(nocut, \"asdf \u003cfoo\")\nfmt.Println(err.Error())\n// Outputs: left unparsed: \u003cfoo\n\n// with a cut, once we see the open tag we know there must be a close tag that matches it, so the parser will error\ncut := Many(Any(Seq(\"\u003c\", Cut(), alpha, \"\u003e\"), alpha))\n_, err = Run(cut, \"asdf \u003cfoo\")\nfmt.Println(err.Error())\n// Outputs: offset 9: expected \u003e\n```\n\n### prior art\n\nInspired by https://github.com/prataprc/goparsec\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvektah%2Fgoparsify","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvektah%2Fgoparsify","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvektah%2Fgoparsify/lists"}