{"id":16554556,"url":"https://github.com/tyrrrz/gress","last_synced_at":"2025-04-09T05:11:34.249Z","repository":{"id":43910181,"uuid":"169098386","full_name":"Tyrrrz/Gress","owner":"Tyrrrz","description":"Progress reporting toolbox","archived":false,"fork":false,"pushed_at":"2024-10-01T18:47:16.000Z","size":247,"stargazers_count":150,"open_issues_count":0,"forks_count":11,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-10-12T19:51:44.745Z","etag":null,"topics":["aggregator","dotnet","dotnet-core","dotnet-standard","iprogress","mux","muxer","percentage","progress"],"latest_commit_sha":null,"homepage":"","language":"C#","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/Tyrrrz.png","metadata":{"files":{"readme":"Readme.md","changelog":null,"contributing":null,"funding":null,"license":"License.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"Tyrrrz","patreon":"Tyrrrz","custom":["tyrrrz.me/donate"]}},"created_at":"2019-02-04T15:19:32.000Z","updated_at":"2024-10-01T18:47:18.000Z","dependencies_parsed_at":"2023-02-08T20:15:27.207Z","dependency_job_id":"0951ab68-4e65-47b3-9cc8-aa6a01d785c0","html_url":"https://github.com/Tyrrrz/Gress","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tyrrrz%2FGress","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tyrrrz%2FGress/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tyrrrz%2FGress/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tyrrrz%2FGress/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Tyrrrz","download_url":"https://codeload.github.com/Tyrrrz/Gress/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247980843,"owners_count":21027808,"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","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":["aggregator","dotnet","dotnet-core","dotnet-standard","iprogress","mux","muxer","percentage","progress"],"created_at":"2024-10-11T19:51:47.160Z","updated_at":"2025-04-09T05:11:34.220Z","avatar_url":"https://github.com/Tyrrrz.png","language":"C#","funding_links":["https://github.com/sponsors/Tyrrrz","https://patreon.com/Tyrrrz","tyrrrz.me/donate"],"categories":[],"sub_categories":[],"readme":"# Gress\n\n[![Status](https://img.shields.io/badge/status-maintenance-ffd700.svg)](https://github.com/Tyrrrz/.github/blob/master/docs/project-status.md)\n[![Made in Ukraine](https://img.shields.io/badge/made_in-ukraine-ffd700.svg?labelColor=0057b7)](https://tyrrrz.me/ukraine)\n[![Build](https://img.shields.io/github/actions/workflow/status/Tyrrrz/Gress/main.yml?branch=master)](https://github.com/Tyrrrz/Gress/actions)\n[![Coverage](https://img.shields.io/codecov/c/github/Tyrrrz/Gress/master)](https://codecov.io/gh/Tyrrrz/Gress)\n[![Version](https://img.shields.io/nuget/v/Gress.svg)](https://nuget.org/packages/Gress)\n[![Downloads](https://img.shields.io/nuget/dt/Gress.svg)](https://nuget.org/packages/Gress)\n[![Discord](https://img.shields.io/discord/869237470565392384?label=discord)](https://discord.gg/2SUWKFnHSm)\n[![Fuck Russia](https://img.shields.io/badge/fuck-russia-e4181c.svg?labelColor=000000)](https://twitter.com/tyrrrz/status/1495972128977571848)\n\n\u003ctable\u003e\n    \u003ctr\u003e\n        \u003ctd width=\"99999\" align=\"center\"\u003eDevelopment of this project is entirely funded by the community. \u003cb\u003e\u003ca href=\"https://tyrrrz.me/donate\"\u003eConsider donating to support!\u003c/a\u003e\u003c/b\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n\u003c/table\u003e\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"favicon.png\" alt=\"Icon\" /\u003e\n\u003c/p\u003e\n\n**Gress** is a library that extends the standard [`IProgress\u003cT\u003e`](https://docs.microsoft.com/en-us/dotnet/api/system.iprogress-1) interface with a set of utilities for collecting, transforming, filtering, and multiplexing progress updates in your code.\n\n## Terms of use\u003csup\u003e[[?]](https://github.com/Tyrrrz/.github/blob/master/docs/why-so-political.md)\u003c/sup\u003e\n\nBy using this project or its source code, for any purpose and in any shape or form, you grant your **implicit agreement** to all the following statements:\n\n- You **condemn Russia and its military aggression against Ukraine**\n- You **recognize that Russia is an occupant that unlawfully invaded a sovereign state**\n- You **support Ukraine's territorial integrity, including its claims over temporarily occupied territories of Crimea and Donbas**\n- You **reject false narratives perpetuated by Russian state propaganda**\n\nTo learn more about the war and how you can help, [click here](https://tyrrrz.me/ukraine). Glory to Ukraine! 🇺🇦\n\n## Install\n\n- 📦 [NuGet](https://nuget.org/packages/Gress): `dotnet add package Gress`\n\n## Screenshots\n\n![demo](.assets/demo.png)\n\n## Usage\n\n### Percentage type\n\nTo make progress updates more explicit, **Gress** provides a universal progress unit — the `Percentage` type.\nUnlike the primitive numeric types that are commonly paired with `IProgress\u003cT\u003e`, this type can be used to unambiguously represent progress as a ratio of completed work to the total amount of work encapsulated by a given operation.\n\nAn instance of `Percentage` can be created from either a value or a fraction:\n\n```csharp\nusing Gress;\n\n// Mapped from value\nvar fiftyPercent = Percentage.FromValue(50); // 50%\n\n// Mapped from fractional representation\nvar twentyPercent = Percentage.FromFraction(0.2); // 20%\n```\n\nSimilarly, both value and fraction can be extracted from an initialized `Percentage` by accessing the corresponding properties:\n\n```csharp\nusing Gress;\n\nvar fiftyPercent = Percentage.FromValue(50);\n\nvar asValue = fiftyPercent.Value; // 50.0 (double)\nvar asFraction = fiftyPercent.Fraction; // 0.5 (double)\n```\n\nUsing `Percentage` in your `IProgress\u003cT\u003e` handlers lets you communicate progress updates without making any assumptions about their semantics:\n\n```csharp\nusing Gress;\n\nasync Task PerformWorkAsync(IProgress\u003cPercentage\u003e progrss)\n{\n    await Task.Delay(100);\n\n    // Half-way done\n    progress.Report(Percentage.FromValue(50));\n\n    await Task.Delay(100);\n\n    // Finished\n    progress.Report(Percentage.FromFraction(1));\n}\n\n// ...\n\nvar progress = new Progress\u003cPercentage\u003e(p =\u003e Console.WriteLine(p));\nawait PerformWorkAsync(progress);\n\n// Console output:\n// 50,0%\n// 100,0%\n```\n\nWhen interfacing with external methods, however, you may need to provide a specific progress handler required by their signature.\nIn such cases, you can convert an existing percentage-based handler into another type using one of the available extension methods:\n\n```csharp\nusing Gress;\n\nasync Task FooAsync(IProgress\u003cdouble\u003e progress) { /* ... */ }\nasync Task BarAsync(IProgress\u003cint\u003e progress) { /* ... */ }\n\nvar progress = new Progress\u003cPercentage\u003e(p =\u003e /* ... */);\n\nawait FooAsync(progress.ToDoubleBased());\nawait BarAsync(progress.ToInt32Based());\n```\n\nLikewise, you can also perform conversions in the other direction, which can be useful for preserving backwards-compatibility in your own methods:\n\n```csharp\nusing Gress;\n\nasync Task FooAsync(IProgress\u003cdouble\u003e progress)\n{\n    var actualProgress = progress.ToPercentageBased(); // IProgress\u003cPercentage\u003e\n\n    // Reports 0.5 on the original progress handler\n    actualProgress.Report(Percentage.FromFraction(0.5));\n}\n\nasync Task BarAsync(IProgress\u003cint\u003e progress)\n{\n    var actualProgress = progress.ToPercentageBased(); // IProgress\u003cPercentage\u003e\n\n    // Reports 50 on the original progress handler\n    actualProgress.Report(Percentage.FromFraction(0.5));\n}\n```\n\n\u003e **Note**:\n\u003e When converting between percentage-based and double-based handlers, percentages are mapped using their fractional form by default.\n\u003e To override this behavior and map by value instead, use `ToDoubleBased(asFraction: false)` and `ToPercentageBased(asFraction: false)`.\n\n\u003e **Note**:\n\u003e For more complex conversion scenarios, consider using the [`WithTransform(...)`](#transformation) method.\n\n### Terminal handlers\n\nEvery progress reporting chain ultimately ends with a terminal handler, which usually relays the information to the user or stores it somewhere else.\nTo simplify some of the most common scenarios, **Gress** comes with two terminal handlers built in.\n\n#### Progress container\n\nThis handler is an object with a single property, whose value is overwritten with every new progress update that gets reported.\nIt also implements the `INotifyPropertyChanged` interface, allowing the property to be bound from XAML-based user interfaces.\n\nHere's a very basic example of how you would use it in a typical WPF application:\n\n```csharp\npublic class MainViewModel\n{\n    public MainViewModel() =\u003e\n        PerformWorkCommand = new AsyncRelayCommand(PerformWorkAsync);\n\n    public ProgressContainer\u003cPercentage\u003e Progress { get; } = new();\n\n    public IRelayCommand PerformWorkCommand { get; }\n\n    public async Task PerformWorkAsync()\n    {\n        for (var i = 1; i \u003c= 100; i++)\n        {\n            // Simulate work\n            await Task.Delay(200);\n            \n            // Report progress as a value in the 0..100 range\n            Progress.Report(Percentage.FromValue(i));\n        }    \n    }\n}\n```\n\n```xml\n\u003cWindow\n    x:Class=\"MainWindow\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    d:DataContext=\"{d:DesignInstance Type=MainViewModel}\"\u003e\n    \u003cStackPanel\u003e\n        \u003cButton\n            Margin=\"32\"\n            Content=\"Execute\"\n            Command=\"{Binding PerformWorkCommand}\" /\u003e\n\n        \u003cProgressBar\n            Margin=\"32\"\n            Height=\"10\"\n            Minimum=\"0\"\n            Maximum=\"100\"\n            Value=\"{Binding Progress.Current.Value, Mode=OneWay}\" /\u003e\n    \u003c/StackPanel\u003e\n\u003c/Window\u003e\n```\n\n#### Progress collector\n\nThis handler works by storing all reported progress updates in a collection, whose values can be retrieved later.\nIt's primarily designed for testing purposes.\n\nHere's how you can use it to verify that a method reported its progress correctly:\n\n```csharp\n[Fact]\npublic async Task My_method_reports_progress_correctly()\n{\n    // Arrange\n    var progress = new ProgressCollector\u003cPercentage\u003e();\n    var worker = new Worker();\n\n    // Act\n    await worker.PerformWorkAsync(progress);\n\n    // Assert\n    var values = progress.GetValues();\n\n    values.Should().NotBeEmpty(); // not empty\n    values.Should().OnlyHaveUniqueItems(); // no redundant updates\n}\n```\n\n### Composing handlers\n\nExisting progress handlers can be composed into more complex handlers using some of the extension methods that **Gress** offers.\nThese can be used to easily apply transformations, inject filtering logic, or merge multiple handlers together.\n\n#### Transformation\n\nYou can use `WithTransform(...)` to create a handler that transforms all reported progress updates into a different form:\n\n```csharp\nusing Gress;\n\nenum Status { Started, HalfWay, Completed }\n\nvar progress = new Progress\u003cPercentage\u003e(p =\u003e /* ... */);\n\n// Transform into a progress handler that accepts an enum value and maps\n// it into a value of the original type\nvar transformedProgress = progress.WithTransform((Status s) =\u003e s switch\n{\n    Status.Completed =\u003e Percentage.FromValue(100), // 100%\n    Status.HalfWay =\u003e Percentage.FromValue(50), // 50%\n    _ =\u003e Percentage.FromValue(0) // 0%\n}); // IProgress\u003cStatus\u003e\n\n// Effectively reports 50% on the original handler\ntransformedProgress.Report(Status.HalfWay);\n```\n\nA simpler overload of the above method can also be used when transforming between values of the same type:\n\n```csharp\nusing Gress;\n\nvar progress = new Progress\u003cint\u003e(p =\u003e /* ... */);\n\nvar transformedProgress = progress.WithTransform(p =\u003e 5 * p); // IProgress\u003cint\u003e\n\n// Effectively reports 50% on the original handler\ntransformedProgress.Report(10);\n```\n\n\u003e **Note**:\n\u003e Method `WithTransform(...)` bears some resemblance to LINQ's `Select(...)`, however they are not completely equivalent.\n\u003e The main difference is that the flow of data in `IProgress\u003cT\u003e` is inverse to that of `IEnumerable\u003cT\u003e`, which means that the transformations in `WithTransform(...)` are applied in the opposite direction.\n\n#### Filtering\n\nYou can use `WithFilter(...)` to create a handler that drops progress updates that don't satisfy a predicate:\n\n```csharp\nusing Gress;\n\nvar progress = new Progress\u003cPercentage\u003e(p =\u003e /* ... */);\n\n// Filter out values below 10%\nvar filteredProgress = progress.WithFilter(p =\u003e p.Fraction \u003e= 0.1);\n\nfilteredProgress.Report(Percentage.FromFraction(0.05)); // ✖\nfilteredProgress.Report(Percentage.FromFraction(0.25)); // ✓\n```\n\n#### Deduplication\n\nYou can use `WithDeduplication(...)` to create a handler that filters out consecutive progress updates with the same value:\n\n```csharp\nusing Gress;\n\nvar progress = new Progress\u003cPercentage\u003e(p =\u003e /* ... */);\n\nvar deduplicatedProgress = progress.WithDeduplication();\n\ndeduplicatedProgress.Report(Percentage.FromFraction(0.1)); // ✓\ndeduplicatedProgress.Report(Percentage.FromFraction(0.3)); // ✓\ndeduplicatedProgress.Report(Percentage.FromFraction(0.3)); // ✖\ndeduplicatedProgress.Report(Percentage.FromFraction(0.3)); // ✖\ndeduplicatedProgress.Report(Percentage.FromFraction(0.5)); // ✓\n```\n\n#### Ordering\n\nYou can use `WithOrdering(...)` to create a handler that filters out progress updates that arrive out of order:\n\n```csharp\nusing Gress;\n\nvar progress = new Progress\u003cPercentage\u003e(p =\u003e /* ... */);\n\nvar orderedProgress = progress.WithOrdering();\n\norderedProgress.Report(Percentage.FromFraction(0.1)); // ✓\norderedProgress.Report(Percentage.FromFraction(0.3)); // ✓\norderedProgress.Report(Percentage.FromFraction(0.2)); // ✖\norderedProgress.Report(Percentage.FromFraction(0.5)); // ✓\norderedProgress.Report(Percentage.FromFraction(0.4)); // ✖\n```\n\n#### Merging\n\nYou can use `Merge(...)` to combine multiple progress handlers into one:\n\n```csharp\nusing Gress;\n\nvar progress1 = new Progress\u003cPercentage\u003e(p =\u003e /* ... */);\nvar progress2 = new Progress\u003cPercentage\u003e(p =\u003e /* ... */);\n\nvar mergedProgress = progress1.Merge(progress2); // IProgress\u003cPercentage\u003e\n\n// Reports 50% on both progress handlers\nmergedProgress.Report(Percentage.FromFraction(0.5));\n```\n\nThis method can also be called on collections:\n\n```csharp\nusing Gress;\n\nvar progresses = new[]\n{\n    new Progress\u003cPercentage\u003e(p =\u003e /* ... */),\n    new Progress\u003cPercentage\u003e(p =\u003e /* ... */),\n    new Progress\u003cPercentage\u003e(p =\u003e /* ... */),\n    new Progress\u003cPercentage\u003e(p =\u003e /* ... */)\n};\n\nvar mergedProgress = progresses.Merge(); // IProgress\u003cPercentage\u003e\n\n// Reports 50% on all progress handlers\nmergedProgress.Report(Percentage.FromFraction(0.5));\n```\n\n### Multiplexing\n\nMultiplexing allows a single handler to aggregate progress updates from multiple input sources.\nThis is useful when you want to encapsulate several progress-reporting operations in a single higher-order operation.\n\nTo do this, create a muxer for the target progress handler and then use it to assign an input for each operation:\n\n```csharp\nusing Gress;\n\nvar progress = new Progress\u003cPercentage\u003e(p =\u003e /* ... */);\n\nvar muxer = progress.CreateMuxer();\nvar subProgress1 = muxer.CreateInput(); // IProgress\u003cPercentage\u003e\nvar subProgress2 = muxer.CreateInput(); // IProgress\u003cPercentage\u003e\nvar subProgress3 = muxer.CreateInput(); // IProgress\u003cPercentage\u003e\n```\n\nWhen a progress update is reported on any of these inputs, all the updates up to that point are aggregated into one and routed to the target handler.\nThe sample below illustrates this process in detail:\n\n```csharp\n// ...\n\nsubProgress1.Report(Percentage.FromFraction(0.5));\n\n// Input 1 -\u003e  50%\n// Input 2 -\u003e   0%\n// Input 3 -\u003e   0%\n// Total   -\u003e ~17%\n\nsubProgress1.Report(Percentage.FromFraction(1));\nsubProgress2.Report(Percentage.FromFraction(0.75));\n\n// Input 1 -\u003e 100%\n// Input 2 -\u003e  75%\n// Input 3 -\u003e   0%\n// Total   -\u003e ~58%\n\nsubProgress2.Report(Percentage.FromFraction(1));\nsubProgress3.Report(Percentage.FromFraction(0.9));\n\n// Input 1 -\u003e 100%\n// Input 2 -\u003e 100%\n// Input 3 -\u003e  90%\n// Total   -\u003e ~97%\n\nsubProgress3.Report(Percentage.FromFraction(1));\n\n// Input 1 -\u003e 100%\n// Input 2 -\u003e 100%\n// Input 3 -\u003e 100%\n// Total   -\u003e 100%\n```\n\nAdditionally, since muxer inputs are progress handlers themselves, they can be multiplexed further as well.\nDoing this allows you to create hierarchical progress reporting chains:\n\n```csharp\nusing Gress;\n\nasync Task PerformWorkAsync(IProgress\u003cPercentage\u003e progress)\n{\n    for (var i = 1; i \u003c= 100; i++)\n    {\n        await Task.Delay(200);\n        progress.Report(Percentage.FromValue(i));\n    }\n}\n\nasync Task FooAsync(IProgress\u003cPercentage\u003e progress)\n{\n    var muxer = progress.CreateMuxer();\n    var subProgress1 = muxer.CreateInput();\n    var subProgress2 = muxer.CreateInput();\n\n    await Task.WhenAll(\n        PerformWorkAsync(subProgress1),\n        PerformWorkAsync(subProgress2)\n    );\n}\n\nasync Task BarAsync(IProgress\u003cPercentage\u003e progress)\n{\n    var muxer = progress.CreateMuxer();\n    var subProgress1 = muxer.CreateInput();\n    var subProgress2 = muxer.CreateInput();\n    var subProgress3 = muxer.CreateInput();\n\n    await Task.WhenAll(\n        FooAsync(subProgress1),\n        FooAsync(subProgress2),\n        FooAsync(subProgress3)\n    );\n}\n```\n\n\u003e **Note**:\n\u003e Muxing is only available on percentage-based handlers because it relies on their ability to represent progress as a relative fraction.\n\u003e If required, you can convert certain other handlers into percentage-based handlers using the `ToPercentageBased()` extension method.\n\n#### Custom weights\n\nA muxer input may be assigned a custom weight modifier, which determines its priority in relation to others.\nProgress reported on an input with higher weight influences the aggregated progress to a greater degree and vice versa.\n\nYou can specify the input weight by passing it to the `CreateInput(...)` method:\n\n```csharp\nusing Gress;\n\nvar progress = new Progress\u003cPercentage\u003e(p =\u003e /* ... */);\n\nvar muxer = progress.CreateMuxer();\nvar subProgress1 = muxer.CreateInput(1);\nvar subProgress2 = muxer.CreateInput(4);\n\n// Weight split:\n// Input 1 -\u003e 20% of total\n// Input 2 -\u003e 80% of total\n\nsubProgress1.Report(Percentage.FromFraction(0.9));\nsubProgress2.Report(Percentage.FromFraction(0.3));\n\n// Input 1 -\u003e 90% (less important)\n// Input 2 -\u003e 30% (more important)\n// Total   -\u003e 42% (would've been 60% without weights)\n```\n\n#### Auto-reset muxer\n\nIn some cases, you may need to report progress on an infinite workflow where new operations are started and completed in a continuous fashion.\nThis can be achieved by using an auto-reset muxer.\n\nInputs to an auto-reset muxer implement the `ICompletableProgress\u003cT\u003e` interface and are capable of reporting completion after all of the underlying work is finished.\nOnce all connected inputs report completion, they are disconnected from the muxer and the latter is reset back to the initial state.\n\nTo create an auto-reset muxer, call `WithAutoReset()` on an existing instance:\n\n```csharp\nusing Gress;\nusing Gress.Completable;\n\nvar progress = new Progress\u003cPercentage\u003e(p =\u003e /* ... */);\n\nvar muxer = progress.CreateMuxer().WithAutoReset();\nvar subProgress1 = muxer.CreateInput(); // ICompletableProgress\u003cPercentage\u003e\nvar subProgress2 = muxer.CreateInput(); // ICompletableProgress\u003cPercentage\u003e\n\nsubProgress1.Report(Percentage.FromFraction(0.3));\nsubProgress2.Report(Percentage.FromFraction(0.9));\n\n// Input 1 -\u003e 30%\n// Input 2 -\u003e 90%\n// Total   -\u003e 60%\n\nsubProgress1.Report(Percentage.FromFraction(1));\nsubProgress1.ReportCompletion();\n\n// Input 1 -\u003e 100% (completed)\n// Input 2 -\u003e 90%\n// Total   -\u003e 95%\n\nsubProgress2.Report(Percentage.FromFraction(1));\nsubProgress2.ReportCompletion();\n\n// All inputs disconnected\n// Total   -\u003e 0%\n\nvar subProgress3 = muxer.CreateInput();\nsubProgress3.Report(Percentage.FromFraction(0.5));\n\n// Input 3 -\u003e 50%\n// Total   -\u003e 50%\n```\n\n\u003e **Note**:\n\u003e You can wrap an instance of `ICompletableProgress\u003cT\u003e` in a disposable container by calling `ToDisposable()`.\n\u003e This allows you to place the handler in a `using (...)` block, which ensures that the completion is always reported at the end.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftyrrrz%2Fgress","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftyrrrz%2Fgress","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftyrrrz%2Fgress/lists"}