{"id":20340378,"url":"https://github.com/toolisticon/fluapigen","last_synced_at":"2025-10-30T05:12:12.284Z","repository":{"id":46110085,"uuid":"427373380","full_name":"toolisticon/FluApiGen","owner":"toolisticon","description":"An annotation processor to easily generate implementations of  complex, immutable fluent apis","archived":false,"fork":false,"pushed_at":"2025-09-04T08:35:40.000Z","size":669,"stargazers_count":5,"open_issues_count":15,"forks_count":2,"subscribers_count":4,"default_branch":"develop","last_synced_at":"2025-09-04T10:33:22.665Z","etag":null,"topics":["annotation-processor","annotations","api","code-generation","fluent","fluent-api","generator","immutable","java","kotlin"],"latest_commit_sha":null,"homepage":"","language":"Java","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/toolisticon.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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}},"created_at":"2021-11-12T13:40:33.000Z","updated_at":"2025-09-04T08:35:43.000Z","dependencies_parsed_at":"2024-11-14T21:24:10.644Z","dependency_job_id":"e24fedea-93fa-4ac3-9ac9-4eef700ad2e8","html_url":"https://github.com/toolisticon/FluApiGen","commit_stats":null,"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"purl":"pkg:github/toolisticon/FluApiGen","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toolisticon%2FFluApiGen","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toolisticon%2FFluApiGen/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toolisticon%2FFluApiGen/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toolisticon%2FFluApiGen/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/toolisticon","download_url":"https://codeload.github.com/toolisticon/FluApiGen/tar.gz/refs/heads/develop","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toolisticon%2FFluApiGen/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":281748716,"owners_count":26554822,"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-10-30T02:00:06.501Z","response_time":61,"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":["annotation-processor","annotations","api","code-generation","fluent","fluent-api","generator","immutable","java","kotlin"],"created_at":"2024-11-14T21:21:34.587Z","updated_at":"2025-10-30T05:12:12.277Z","avatar_url":"https://github.com/toolisticon.png","language":"Java","readme":"# FLUAPIGEN - The Fluent API Generator\n\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.toolisticon.fluapigen/fluapigen/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.toolisticon.fluapigen/fluapigen)\n[![Sonatype Central](https://maven-badges.sml.io/sonatype-central/io.toolisticon.fluapigen/fluapigen/badge.svg)](https://maven-badges.sml.io/sonatype-central/io.toolisticon.fluapigen/fluapigen)\n[![default](https://github.com/toolisticon/FluApiGen/actions/workflows/default.yml/badge.svg)](https://github.com/toolisticon/FluApiGen/actions/workflows/default.yml)\n[![release_on_master](https://github.com/toolisticon/FluApiGen/actions/workflows/release.yml/badge.svg?branch=master)](https://github.com/toolisticon/FluApiGen/actions/workflows/release.yml)\n[![codecov](https://codecov.io/gh/toolisticon/FluApiGen/branch/develop/graph/badge.svg?token=FlcugFxC64)](https://codecov.io/gh/toolisticon/FluApiGen)\n[![javadoc](https://javadoc.io/badge2/io.toolisticon.fluapigen/fluapigen-api/javadoc.svg)](https://javadoc.io/doc/io.toolisticon.fluapigen/fluapigen-api)\n\nImplementing and especially maintaining of fluent and immutable apis is one of a most annoying and difficult tasks to do in java developing.\n\nYou usually have to implement a lot of boilerplate code that is only needed to handle and clone the fluent apis internal state.\nRefactoring of an existing fluent api can therefore be a very complex thing to do.\n\nThis project provides an annotation processor that generates fluent api implementations and therefore completely hiding all necessary boilerplate code.\nTo achieve this all that needs to be done is to define some fluent and backing bean interfaces and command classes and to configure its \"plumbing\" by placing a few annotations.\n\n\n# Features\n- fluent api is created by defining some interfaces and placing some annotations on them\n- this projects annotation processor then generates the fluent api implementation for you\n- it's possible to use converters and validators on fluent api parameters\n- implementing, extending and maintaining of an immutable, fluent api becomes a no-brainer\n\n# Restrictions\n- backing bean interfaces must not contain any cycles and must be strongly hierarchical\n\n# How does it work?\n\n## Project Setup\n\nThe api lib must be bound as a dependency - for example in maven:\n```xml\n\u003cdependencies\u003e\n\n    \u003cdependency\u003e\n        \u003cgroupId\u003eio.toolisticon.fluapigen\u003c/groupId\u003e\n        \u003cartifactId\u003efluapigen-api\u003c/artifactId\u003e\n        \u003cversion\u003e1.0.0\u003c/version\u003e\n        \u003cscope\u003eprovided\u003c/scope\u003e\n    \u003c/dependency\u003e\n\n    \u003c!-- optional - add it if you want to use validators --\u003e\n    \u003cdependency\u003e\n        \u003cgroupId\u003eio.toolisticon.fluapigen\u003c/groupId\u003e\n        \u003cartifactId\u003efluapigen-validation-api\u003c/artifactId\u003e\n        \u003cversion\u003e1.0.0\u003c/version\u003e\n        \u003cscope\u003ecompile\u003c/scope\u003e\n    \u003c/dependency\u003e\n \n\u003c/dependencies\u003e\n```\n\nAdditionally, you need to declare the annotation processor path in your compiler plugin:\n\n```xml\n\u003cplugin\u003e\n    \u003cartifactId\u003emaven-compiler-plugin\u003c/artifactId\u003e\n\n    \u003cconfiguration\u003e\n        \n        \u003cannotationProcessorPaths\u003e\n            \u003cpath\u003e\n                \u003cgroupId\u003eio.toolisticon.fluapigen\u003c/groupId\u003e\n                \u003cartifactId\u003efluapigen-processor\u003c/artifactId\u003e\n                \u003cversion\u003e1.0.0\u003c/version\u003e\n            \u003c/path\u003e\n        \u003c/annotationProcessorPaths\u003e\n        \n    \u003c/configuration\u003e\n\n\u003c/plugin\u003e\n```\n\n## Documentation\nBasically you have to create a class annotated with the _FluentApi_ annotation which takes the fluent apis base class name as attribute.\n\n```java\n@FluentApi(\"CuteFluentApiStarter\")\npublic class CuteFluentApi {\n    // Contains the interfaces for \n    // backing beans \n    // and fluent api \n    // and classes for closing commands.\n}\n```\n\nThe fluent api can be defined inside that class by using three kinds of components:\n- the fluent api interfaces\n- the backing bean interfaces which are storing the configuration data passed in via the fluent api interfaces\n- command classes which are taking the root backing bean as a parameter and doing things with it\n\nThe following diagram demonstrates how those components relate to each other:\n\n![Fluapigen Setup](/documentation/Fluapigen.png)\n\nThe development of the fluent api can be broke down to 3 different steps.\n\n### Defining The Backing Bean\nThe backing bean interfaces are defining the configuration (or in other word context) to be build.\nIt basically defines the getter methods for all values used by the fluent api.\nTherefore, all methods must have a non-void return type and must not have any parameters.\n\nThe backing bean interfaces must be annotated with the _FluentApiBackingBean_ annotation.\nThe value getter methods may be annotated with the _FluentApiBackingBeanField_ annotation.\nBy doing this it's possible to declare an id for the field and its initial value.\nIf the id is not explicitly it will use the fields method name as a fallback.\nField ids must be unique in the interface.\n\n```java\n@FluentApiBackingBean\npublic interface CompilerTest {\n\n    // annotation is optional - ids default to method name\n    @FluentApiBackingBeanField(\"value1\")\n    String getValue1();\n\n    // embedded Backing Bean\n    @FluentApiBackingBeanField(\"embeddedBackingBean\")\n    List\u003cCompilerMessageCheck\u003e compilerMessageChecks();\n\n}\n```\n\nChild backing beans can be defined as single values or as Collection types of List or Set.\n\n### Defining Closing Commands\nClosing commands can be defined by defining static inner classes annotated with the _FluentApiCommand_ annotation.\nA command class must contain exactly one static method which takes the backing bean as the only parameter.\n\nIt's later possible to link a fluent api method to a closing command.\nThe fluent api method call then will be forwarded to the closing command.\n\n```java\n@FluentApiCommand\npublic static class ExecuteTestCommand {\n    public static void myCommand(CompilerTest backingBean) {\n        // do something\n    }\n}\n```\n\nCommands may have a return value.\n\n### Defining The Fluent Api\n\nThe fluent api must be defined in multiple interfaces annotated with the _FluentApiInterface_ annotation.\nEach interface will be bound to one specific backing bean. A backing bean can be bound by multiple fluent interfaces.\n\nMethods defined in those interfaces are only allowed to return other fluent api interfaces, except for closing commands.\n\nAll method parameters and methods that close a backing bean creation must be annotated with the _FluentApiBackingBeanField_ annotation which defines which value should be written.\nOf course parameter and value type must match or a correct converter must be used. \n\nDefault methods will completely be ignored by the processor and can be used to transform parameters and to delegate calls to another interface method. \n\nThere must be exactly one interface annotated with the _FluentApiRoot_ annotation. \nThose interfaces methods will be available as static starter methods for the fluent api.\n\nPlease check the following example for further information - because the code explains itself pretty well.\n\n\n## Example\n\nThis is a small example related to the [toolisticon CUTE]() project.\nCUTE a compile testing framework for testing annotation processors that allows you to configure test by using an immutable fluent api.\nIn this example checks for compiler outcome and for specific compiler messages can be defined.\n\n```java\npackage io.toolisticon.fluapigen.integrationtest;\n\n\nimport io.toolisticon.fluapigen.api.FluentApi;\nimport io.toolisticon.fluapigen.api.FluentApiBackingBean;\nimport io.toolisticon.fluapigen.api.FluentApiBackingBeanField;\nimport io.toolisticon.fluapigen.api.FluentApiBackingBeanMapping;\nimport io.toolisticon.fluapigen.api.FluentApiCommand;\nimport io.toolisticon.fluapigen.api.FluentApiImplicitValue;\nimport io.toolisticon.fluapigen.api.FluentApiInterface;\nimport io.toolisticon.fluapigen.api.FluentApiParentBackingBeanMapping;\nimport io.toolisticon.fluapigen.api.FluentApiRoot;\n\nimport java.util.List;\n\n@FluentApi(\"CuteFluentApiStarter\")\npublic class CuteFluentApi {\n\n    @FluentApiBackingBean\n    public interface CompilerTest {\n\n        String testType();\n\n        Boolean compilationSucceeded();\n\n        List\u003cCompilerMessageCheck\u003e compilerMessageChecks();\n\n    }\n\n    @FluentApiBackingBean\n    public interface CompilerMessageCheck {\n\n        CompilerMessageScope compilerMessageScope();\n\n        CompilerMessageComparisonType compilerMessageComparisonType();\n\n        String searchString();\n\n        Integer atLine();\n\n    }\n    \n    public enum TestType {\n        UNIT,\n        BLACK_BOX\n    }\n\n    public enum CompilerMessageScope {\n        NOTE,\n        WARNING,\n        MANDATORY_WARNING,\n        ERROR;\n    }\n\n    public enum CompilerMessageComparisonType {\n        CONTAINS,\n        EQUALS;\n    }\n\n    @FluentApiInterface(CompilerTest.class)\n    @FluentApiRoot\n    public interface MyRootInterface {\n\n        @FluentApiImplicitValue(id = \"testType\", value = \"UNIT\")\n        CompilerTestInterface unitTest();\n\n        @FluentApiImplicitValue(id = \"testType\", value = \"BLACK_BOX\")\n        CompilerTestInterface blackBoxTest();\n\n    }\n\n    @FluentApiInterface(CompilerTest.class)\n    public interface CompilerTestInterface {\n\n\n        @FluentApiImplicitValue(id = \"compilationSucceeded\", value = \"true\")\n        CompilerTestInterface compilationShouldSucceed();\n\n        @FluentApiImplicitValue(id = \"compilationSucceeded\", value = \"false\")\n        CompilerTestInterface compilationShouldFail();\n\n        CompilerMessageCheckMessageType expectCompilerMessage();\n\n        @FluentApiCommand(ExecuteTestCommand.class)\n        void executeTest();\n\n    }\n\n    @FluentApiInterface(CompilerMessageCheck.class)\n    public interface CompilerMessageCheckMessageType {\n\n        @FluentApiImplicitValue(id = \"compilerMessageScope\", value = \"NOTE\")\n        CompilerMessageCheckComparisonType asNote();\n\n        @FluentApiImplicitValue(id = \"compilerMessageScope\", value = \"WARNING\")\n        CompilerMessageCheckComparisonType asWarning();\n\n        @FluentApiImplicitValue(id = \"compilerMessageScope\", value = \"MANDATORY_WARNING\")\n        CompilerMessageCheckComparisonType asMandatoryWarning();\n\n        @FluentApiImplicitValue(id = \"compilerMessageScope\", value = \"ERROR\")\n        CompilerMessageCheckComparisonType asError();\n\n    }\n\n    @FluentApiInterface(CompilerMessageCheck.class)\n    public interface CompilerMessageCheckComparisonType {\n\n        @FluentApiImplicitValue(id = \"compilerMessageComparisonType\", value = \"CONTAINS\")\n        @FluentApiParentBackingBeanMapping(value = \"compileMessageChecks\")\n        CompilerTestInterface thatContains(@FluentApiBackingBeanMapping(value = \"searchString\") String text);\n        \n        @FluentApiImplicitValue(id = \"compilerMessageComparisonType\", value = \"EQUALS\")\n        @FluentApiParentBackingBeanMapping(value = \"compileMessageChecks\")\n        CompilerTestInterface thatEquals(@FluentApiBackingBeanMapping(value = \"searchString\") String text);\n        \n        CompilerMessageCheckComparisonType atLine(@FluentApiBackingBeanMapping(value = \"atLine\")Integer line);\n    }\n\n    // Commands\n    @FluentApiCommand\n    public static class ExecuteTestCommand {\n        static void myCommand(CompilerTest backingBean) {\n            /// ...\n        }\n    }\n\n\n}\n```\n\nThe fluent api can then be used as followed:\n\n```java\nCuteFluentApiStarter.unitTest()\n    .compilationShouldSucceed()\n    .expectCompilerMessage().asError().atLine(10).thatContains(\"ABC\")\n    .expectCompilerMessage().asError().thatContains(\"DEF\")\n    .executeTest();\n```\n\n## Advanced Techniques\n\n### Inline Backing Bean Mappings\nSometimes you have smaller backing beans which you might want to declare inline by using multiple parameters of the fluent api method.\nThe fluent api generator provides you the possibility to do that.\nYou have to add the _FluentApiInlineBackingBeanMapping_ annotation to the method and set the target of the corresponding _FluentApiBackingBeanMapping_ to INLINE.\n\n```java\n@FluentApiInterface(value = MyBackingBean.class)\npublic interface MyFluentInterface {\n    \n    // Converters in backing bean mappings\n    @FluentApiInlineBackingBeanMapping(\"nameOfTargetBackingBeanField\")\n    MyFluentInterface doSomething(\n            @FluentApiBackingBeanMapping(value = \"value1\", target=TargetBackingBean.INLINE) Long value1,\n            @FluentApiBackingBeanMapping(value = \"value2\", target=TargetBackingBean.INLINE) Long value2\n    );\n\n}\n```\nIn this example an inline backing bean will be set at _MyBackingBean.nameOfTargetBackingBeanField_.\nIt's _value1_ and _value2_ attributes will be mapped from the corresponding method parameters.\n\n### Converters\nConverters can be used to map input parameters from either implicit value annotations or backing bean mappings to the backing bean type.\n\nAn example Converter:\n```java\npublic static class TargetType {\n\n    private final String value;\n\n    public TargetType (String value) {\n        this.value = value;\n    }\n\n    @Override\n    public String toString() {\n        return this.value;\n    }\n}\n\npublic static class MyStringConverter implements FluentApiConverter\u003cString,TargetType\u003e {\n\n    @Override\n    public TargetType convert(String o) {\n        return new TargetType(o);\n    }\n\n}\n\npublic static class MyLongConverter implements FluentApiConverter\u003cString, TargetType\u003e {\n\n    @Override\n    public TargetType convert(Long o) {\n        // not null save ;)\n        return new TargetType(o.toString());\n    }\n\n}\n\n@FluentApiInterface(value = MyBackingBean.class)\npublic interface MyFluentInterface {\n    \n    // Converters in implicit value annotation\n    @FluentApiImplicitValue(id = \"singleValue\", value = \"SINGLE\", converter = MyStringConverter.class)\n    MyFluentInterface setSingleValue();\n    \n    // Converters in backing bean mappings\n    MyFluentInterface setViaLongValue(@FluentApiBackingBeanMapping(value = \"singleValue\", converter = MyLongConverter.class) Long longValue);\n\n}\n```\n### Using validators at fluent interface method parameters\nFluApiGen provides some basic validators that can be applied to the fluent method parameters at runtime.\n\n- _Matches_ : Regular Expression validator for Strings\n- _MinLength / MaxLength_ : Checks length of Strings or size of Arrays or Collections\n- _NotEmpty_ : Checks if String, array and Collection are not empty\n- _NotNull_ : Checks if passed argument isn't null\n\nTo be able to use validators, the _fluapigen_validation_api_ library must be linked at compile and runtime.\n\n````java\nMyRootInterface setName(@Matches(\"Max.*\") @FluentApiBackingBeanMapping(\"name\") String name);\n````\n\nIn this example the fluent api would throw a _ValidatorException_ if the name doesn't start with \"Max\".\n\nIt's possible to use multiple validators on a parameter and even to provide custom validators:\n\n````java\n@FluentApiValidator(value = Matches.ValidatorImpl.class, parameterNames = {\"value\"})\npublic @interface Matches {\n\n    String value();\n\n    class ValidatorImpl implements Validator\u003cString\u003e {\n\n        private final String regularExpression;\n\n        public ValidatorImpl(String regularExpression) {\n            this.regularExpression = regularExpression;\n        }\n\n        @Override\n        public boolean validate(String obj) {\n            return Pattern.compile(regularExpression).matcher(obj).matches();\n        }\n\n    }\n\n}\n````\n\nValidators are annotations annotated with _FluentApiValidator_ meta annotation. The _FluentApiValidator_ annotation is used to reference the validators implementation class that must implement the _Validator_ interface.\nValidation criteria can be added as annotation attributes. The _FluentApiValidator_ meta annotation defines the attribute to validator constructor mapping via the parameterNames attribute.\nThe validator implementation must provide a matching constructor.\n\n### Javas default methods in fluent api and backing bean in interfaces\nDefault methods will be ignored during processing of fluent api and backing bean interfaces and can be used for different tasks:\n\n- They can provide alternative ways to set a parameter by providing conversions of input parameters and internally calling fluent api methods.\n- They can also be very helpful to provide methods for accessing/filtering/aggregate backing bean attributes.\n \n### Using parent interfaces to share method declarations and backing bean fields declarations\nIt's possible to use parent interfaces to share fluent api method declarations in multiple interfaces:\n\n```java\n    @FluentApiBackingBean\n    interface MyRootLevelBackingBean {\n\n        @FluentApiBackingBeanField(\"1st\")\n        FirstInheritedBackingBean get1st();\n\n        @FluentApiBackingBeanField(\"2nd\")\n        SecondInheritedBackingBean get2nd();\n\n    }\n\n\n    // Parent backing bean interface must not be annotated with FluentApiBackingBean annotation\n    interface MyReusedBackingBeanFields {\n\n        @FluentApiBackingBeanField(\"name\")\n        String getName();\n\n    }\n\n\n    @FluentApiBackingBean\n    interface FirstInheritedBackingBean extends MyReusedBackingBeanFields {\n\n        @FluentApiBackingBeanField(\"1st\")\n        String get1st();\n\n    }\n\n    @FluentApiBackingBean\n    interface SecondInheritedBackingBean extends MyReusedBackingBeanFields {\n\n        @FluentApiBackingBeanField(\"2nd\")\n        String get2nd();\n\n    }\n\n\n    // Fluent Api interfaces\n    @FluentApiInterface(MyRootLevelBackingBean.class)\n    @FluentApiRoot\n    public interface MyRootInterface {\n\n        My1stInterface goto1st();\n\n        My2ndInterface goto2nd();\n\n        @FluentApiCommand(MyCommand.class)\n        void myCommand();\n\n\n    }\n\n    // Parent fluent api interface must not be annotated with FluentApiInterface\n    // There must be a related backing bean interface for related attributes\n    // Type Parameters must be used to pass in the followup fluent api interfaces\n    public interface SharedInterface\u003cFLUENT_INTERFACE\u003e {\n\n        // Fluebt followup interface must be returned as type variable\n        FLUENT_INTERFACE setName(@FluentApiBackingBeanMapping(value = \"name\") String name);\n\n    }\n\n    \n    @FluentApiInterface(FirstInheritedBackingBean.class)\n    public interface My1stInterface extends SharedInterface\u003cMy1stInterface\u003e {\n\n        @FluentApiParentBackingBeanMapping(\"1st\")\n        MyRootInterface set1st(@FluentApiBackingBeanMapping(value = \"1st\") String first);\n\n    }\n\n    @FluentApiInterface(SecondInheritedBackingBean.class)\n    public interface My2ndInterface extends SharedInterface\u003cMy2ndInterface\u003e {\n\n        @FluentApiParentBackingBeanMapping(\"2nd\")\n        MyRootInterface set1st(@FluentApiBackingBeanMapping(value = \"2nd\") String second);\n\n    }\n\n    // Commands\n    @FluentApiCommand\n    static class MyCommand {\n        static void myCommand(MyRootLevelBackingBean backingBean) {\n            System.out.println(\"CHECK\");\n        }\n    }\n```\n\n\n# Contributing\n\nWe welcome any kind of suggestions and pull requests.\n\n## Building and developing the FluApiGen annotation processor\n\nThis project is built using Maven.\nA simple import of the pom in your IDE should get you up and running. To build the project on the commandline, just run `./mvnw` or `./mvnw clean install`\n\n## Requirements\n\nThe likelihood of a pull request being used rises with the following properties:\n\n- You have used a feature branch.\n- You have included a test that demonstrates the functionality added or fixed.\n- You adhered to the [code conventions](http://www.oracle.com/technetwork/java/javase/documentation/codeconvtoc-136057.html).\n\n# License\n\nThis project is released under the revised [MIT License](LICENSE).\n\nThis project includes and repackages the [Annotation-Processor-Toolkit](https://github.com/holisticon/annotation-processor-toolkit) released under the  [MIT License](/3rdPartyLicenses/annotation-processor-toolkit/LICENSE.txt).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftoolisticon%2Ffluapigen","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftoolisticon%2Ffluapigen","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftoolisticon%2Ffluapigen/lists"}