├── .coveralls.yml ├── .github └── ISSUE_TEMPLATE │ └── bug_report.md ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── build.gradle ├── cfg4k-bytebuddy ├── build.gradle └── src │ ├── main │ └── kotlin │ │ └── com │ │ └── jdiazcano │ │ └── cfg4k │ │ └── bytebuddy │ │ └── ByteBuddyConfigProvider.kt │ └── test │ ├── kotlin │ └── com │ │ └── jdiazcano │ │ └── cfg4k │ │ └── bytebuddy │ │ ├── ByteBuddyConfigProviderReloadTest.kt │ │ ├── ByteBuddyConfigProviderTest.kt │ │ ├── NullsConfigProviderTest.kt │ │ ├── TestBinder.kt │ │ └── subpackage │ │ └── Binder.kt │ └── resources │ ├── nulltest.properties │ ├── overridingnulltest.properties │ ├── test.json │ └── test.properties ├── cfg4k-cli ├── build.gradle └── src │ ├── main │ └── kotlin │ │ └── com │ │ └── jdiazcano │ │ └── cfg4k │ │ └── cli │ │ └── CommandLineLoader.kt │ └── test │ └── kotlin │ └── com │ └── jdiazcano │ └── cfg4k │ └── cli │ └── CommandLineLoaderTest.kt ├── cfg4k-core ├── build.gradle └── src │ ├── main │ └── kotlin │ │ └── com │ │ └── jdiazcano │ │ └── cfg4k │ │ ├── binders │ │ ├── Binder.kt │ │ ├── BindingInvocationHandler.kt │ │ ├── Converters.kt │ │ ├── DataClassBinder.kt │ │ └── ProxyBinder.kt │ │ ├── core │ │ └── ConfigObject.kt │ │ ├── loaders │ │ ├── ConfigLoader.kt │ │ ├── DefaultConfigLoader.kt │ │ ├── EnvironmentConfigLoader.kt │ │ ├── PropertyConfigLoader.kt │ │ └── SystemPropertyConfigLoader.kt │ │ ├── parsers │ │ ├── BigParsers.kt │ │ ├── CommonParsers.kt │ │ ├── DateParsers.kt │ │ ├── EnumParser.kt │ │ ├── FileParsers.kt │ │ ├── Parser.kt │ │ ├── Parsers.kt │ │ └── PrimitiveParsers.kt │ │ ├── providers │ │ ├── CachedConfigProvider.kt │ │ ├── ConfigProvider.kt │ │ ├── DefaultConfigProvider.kt │ │ ├── OverrideConfigProvider.kt │ │ ├── Providers.kt │ │ └── ProxyConfigProvider.kt │ │ ├── reloadstrategies │ │ ├── FileChangeReloadStrategy.kt │ │ ├── ReloadStrategy.kt │ │ └── TimedReloadStrategy.kt │ │ ├── sources │ │ ├── ClasspathConfigSource.kt │ │ ├── CommonConfigSources.kt │ │ ├── ConfigSource.kt │ │ ├── FileConfigSource.kt │ │ └── URLConfigSource.kt │ │ └── utils │ │ ├── Exceptions.kt │ │ ├── GenericType.kt │ │ └── TargetType.kt │ └── test │ ├── kotlin │ └── com │ │ └── jdiazcano │ │ └── cfg4k │ │ ├── SharedClasses.kt │ │ ├── binders │ │ └── BindingInvocationHandlerKtTest.kt │ │ ├── core │ │ └── ConfigObjectTest.kt │ │ ├── loaders │ │ ├── EnvironmentConfigLoaderTest.kt │ │ ├── PropertyConfigLoaderTest.kt │ │ └── SystemPropertyConfigLoaderTest.kt │ │ ├── parsers │ │ ├── BigParsersTest.kt │ │ ├── CommonParsersTest.kt │ │ ├── DateParsersTest.kt │ │ ├── EnumParserTest.kt │ │ ├── FileParsersTest.kt │ │ ├── ListParserTest.kt │ │ ├── MapParserTest.kt │ │ ├── ParsersTest.kt │ │ └── PrimiteParsersTest.kt │ │ ├── providers │ │ ├── FullConfigProviderTest.kt │ │ └── OverrideConfigProviderTest.kt │ │ ├── reloadstrategies │ │ ├── FileChangeReloadStrategyTest.kt │ │ └── TimedReloadStrategyTest.kt │ │ ├── sources │ │ ├── ClasspathConfigSourceTest.kt │ │ ├── CommonConfigSourcesTest.kt │ │ ├── ConfigSourceTest.kt │ │ ├── FileConfigSourceTest.kt │ │ └── URLConfigSourceTest.kt │ │ └── utils │ │ ├── TypableTests.kt │ │ └── TypeConverterTest.kt │ └── resources │ ├── book.properties │ ├── classedparser.properties │ ├── doubledproperty.properties │ ├── enumtest.properties │ ├── first.properties │ ├── listtest.properties │ ├── nestedtest.properties │ ├── nulltest.properties │ ├── overridetest.properties │ ├── overridingnulltest.properties │ ├── second.properties │ ├── supernestedtest.properties │ └── test.properties ├── cfg4k-git ├── README.md ├── build.gradle └── src │ ├── main │ └── kotlin │ │ └── com │ │ └── jdiazcano │ │ └── cfg4k │ │ └── loaders │ │ └── git │ │ ├── CustomConfigSessionFactory.kt │ │ └── GitConfigLoader.kt │ └── test │ └── kotlin │ ├── GitConfigLoaderTest.kt │ └── GitConfigProviderTest.kt ├── cfg4k-hocon ├── build.gradle └── src │ ├── main │ └── kotlin │ │ └── com │ │ └── jdiazcano │ │ └── cfg4k │ │ └── hocon │ │ ├── HoconConfigLoader.kt │ │ └── HoconConfigMapper.kt │ └── test │ ├── kotlin │ └── com │ │ └── jdiazcano │ │ └── hocon │ │ ├── HoconConfigLoaderTest.kt │ │ ├── HoconConfigMapperTest.kt │ │ ├── HoconConfigReloaderTest.kt │ │ ├── ProviderWithHocon.kt │ │ └── ProviderWithMaps.kt │ └── resources │ ├── hocon.conf │ ├── hocon2.conf │ └── test.properties ├── cfg4k-json ├── build.gradle └── src │ ├── main │ └── kotlin │ │ └── com │ │ └── jdiazcano │ │ └── cfg4k │ │ └── json │ │ ├── JsonConfigLoader.kt │ │ └── JsonToConfigObject.kt │ └── test │ ├── kotlin │ └── com │ │ └── jdiazcano │ │ └── cfg4k │ │ └── json │ │ ├── ConfigProviderTest.kt │ │ ├── Enumerito.kt │ │ ├── JsonConfigLoaderTest.kt │ │ ├── JsonConfigReloaderTest.kt │ │ └── JsonToConfigObjectMapperTest.kt │ └── resources │ ├── reloadedtest.json │ └── test.json ├── cfg4k-s3 ├── build.gradle └── src │ ├── main │ └── kotlin │ │ └── com │ │ └── jdiazcano │ │ └── cfg4k │ │ └── s3 │ │ └── S3ConfigSource.kt │ └── test │ └── kotlin │ └── com │ └── jdiazcano │ └── cfg4k │ └── s3 │ └── S3ConfigSourceTest.kt ├── cfg4k-schema.png ├── cfg4k-schema.xml ├── cfg4k-yaml ├── build.gradle └── src │ ├── main │ └── kotlin │ │ └── com │ │ └── jdiazcano │ │ └── cfg4k │ │ └── yaml │ │ └── YamlConfigLoader.kt │ └── test │ ├── kotlin │ └── com │ │ └── jdiazcano │ │ └── cfg4k │ │ └── yaml │ │ ├── YamlConfigLoaderTest.kt │ │ ├── YamlConfigMapperTest.kt │ │ └── YamlConfigReloadedTest.kt │ └── resources │ └── test.yml ├── encrypted_cfg4k ├── examples ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src │ └── main │ └── kotlin │ ├── BytebuddyExample.kt │ ├── CustomParser.kt │ ├── FirstExample.kt │ └── utils │ ├── Beans.kt │ └── Utils.kt ├── gradle ├── versions.gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis-ci -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | In order for me to reproduce this behavior I need: 15 | 16 | 1. Version of Cfg4k 17 | 1. ConfigProvider 18 | 1. ConfigLoader 19 | 1. ConfigSource 20 | 1. Configuration files unless the source is a String 21 | 1. Any interfaces/dataclasses used for binding/getting 22 | 23 | As example, it is easy to get an easy prototype with all the 5 points in the list: 24 | ```kotlin 25 | val json = """ 26 | { 27 | "a": "b" 28 | } 29 | """.trimIndent() 30 | val loader = JsonConfigLoader(StringConfigSource(json)) 31 | val provider = DefaultConfigProvider(loader) 32 | 33 | data class MyClass(val a: String) 34 | ``` 35 | 36 | **Expected behavior** 37 | A clear and concise description of what you expected to happen. 38 | 39 | **Additional context** 40 | Add any other context about the problem here. 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### JetBrains template 3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 4 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 5 | 6 | # User-specific stuff: 7 | .idea/ 8 | 9 | # Java 10 | **/build 11 | **/classes 12 | **/out 13 | ktlint 14 | 15 | # Mongo Explorer plugin: 16 | .idea/mongoSettings.xml 17 | 18 | ## File-based project format: 19 | *.iws 20 | 21 | ## Plugin-specific files: 22 | 23 | # mpeltonen/sbt-idea plugin 24 | .idea_modules/ 25 | 26 | # JIRA plugin 27 | atlassian-ide-plugin.xml 28 | 29 | # Crashlytics plugin (for Android Studio and IntelliJ) 30 | com_crashlytics_export_strings.xml 31 | crashlytics.properties 32 | crashlytics-build.properties 33 | fabric.properties 34 | ### Java template 35 | *.class 36 | 37 | # BlueJ files 38 | *.ctxt 39 | 40 | # Mobile Tools for Java (J2ME) 41 | .mtj.tmp/ 42 | 43 | # Package Files # 44 | *.war 45 | *.ear 46 | 47 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 48 | hs_err_pid* 49 | .gradle/ 50 | 51 | *.iml 52 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | languaje: kotlin 2 | jdk: 3 | - openjdk8 4 | - openjdk9 5 | - openjdk10 6 | - openjdk11 7 | - openjdk-ea 8 | 9 | matrix: 10 | allow_failures: 11 | - jdk: openjdk-ea 12 | 13 | sudo: false 14 | 15 | notifications: 16 | email: 17 | on_success: never 18 | on_failure: change 19 | 20 | script: ./gradlew build 21 | 22 | #Caches 23 | before_cache: 24 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 25 | - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ 26 | cache: 27 | directories: 28 | - $HOME/.gradle/caches/ 29 | - $HOME/.gradle/wrapper/ 30 | 31 | before_install: 32 | - mkdir -p ~/.ssh 33 | 34 | after_success: 35 | - ./gradlew junitTest report jacocoRootReport coveralls -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/jdiazcano/cfg4k.svg?branch=master)](https://travis-ci.org/jdiazcano/cfg4k) [![Coverage Status](https://coveralls.io/repos/github/jdiazcano/cfg4k/badge.svg?branch=master)](https://coveralls.io/github/jdiazcano/cfg4k?branch=master) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) [ ![Download](https://api.bintray.com/packages/jdiazcano/cfg4k/cfg4k-core/images/download.svg) ](https://bintray.com/jdiazcano/cfg4k/) 2 | 3 | # News 4 | This repository is (will) be archived as I can no longer maintain it! But @rocketraman has forked it and published to mavencentral: https://github.com/rocketraman/cfg4k please continue using his repo! 5 | 6 | # Overview 7 | Cfg4k is a configuration library made for Kotlin in Kotlin! 8 | 9 | Features 10 | * Automatic reload 11 | * Interface binding 12 | * Ability to handle data classes automatically 13 | * All the complex types and generics are supported 14 | * Huge flexibility, custom sources 15 | * Easy to use 16 | * Bytebuddy provider will be able to compile your bindings at runtime (You will need to add the cfg4k-bytebuddy to your dependencies.) 17 | 18 | For further information, use the [wiki](https://github.com/jdiazcano/cfg4k/wiki) 19 | 20 | # Quick start 21 | 1. Add the Bintray repository: 22 | ```groovy 23 | repositories { 24 | jcenter() 25 | } 26 | ``` 27 | 28 | 2. Add the dependency for the module(s) that you are going to use 29 | ``` 30 | compile 'com.jdiazcano.cfg4k:cfg4k-core:$VERSION' 31 | ``` 32 | 33 | ```kotlin 34 | fun main(args: Array) { 35 | val source = ClassPathConfigSource("global.properties") // Create source 36 | val loader = PropertyConfigLoader(source) // Create loader 37 | val provider = ProxyConfigProvider(loader) // Create provider 38 | val databaseConfig = provider.bind("database") // bind and use 39 | 40 | println("Name: ${databaseConfig.name()}") 41 | println("Url: ${databaseConfig.url()}") 42 | println("Port: ${databaseConfig.port()}") 43 | } 44 | 45 | /** 46 | * This interface defines a database configuration 47 | */ 48 | interface DatabaseConfig { 49 | /** 50 | * You can have javadocs inside your properties and this is really cool 51 | */ 52 | fun url(): String 53 | fun port(): Int 54 | fun name(): String 55 | 56 | // if you have an unused property you know it and you can delete it 57 | val unused: String 58 | 59 | @Deprecated("You can even deprecate properties!") 60 | fun deprecated(): Boolean 61 | 62 | val youCanuseValuesToo: String 63 | val andNullables: Int? 64 | } 65 | ``` 66 | 67 | # Architeture overview 68 | 69 | ![Lightbox](https://raw.githubusercontent.com/jdiazcano/cfg4k/master/cfg4k-schema.png) 70 | 71 | # License 72 | Licensed under the Apache License, Version 2.0. See LICENSE file. 73 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | 3 | apply from: 'gradle/versions.gradle' 4 | 5 | repositories { 6 | mavenLocal() 7 | mavenCentral() 8 | jcenter() 9 | maven { url 'https://jetbrains.jfrog.io/jetbrains/spek-snapshots' } 10 | } 11 | 12 | dependencies { 13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.jetbrains.kotlin" 14 | classpath "org.junit.platform:junit-platform-gradle-plugin:$versions.junitrunner" 15 | } 16 | 17 | } 18 | 19 | plugins { 20 | id 'com.github.kt3k.coveralls' version '2.10.1' 21 | id 'com.github.ben-manes.versions' version '0.28.0' 22 | id "com.jfrog.bintray" version "1.8.4" 23 | } 24 | 25 | apply plugin: 'jacoco' 26 | apply plugin: 'idea' 27 | 28 | idea { 29 | module { 30 | downloadJavadoc = true 31 | downloadSources = true 32 | } 33 | } 34 | 35 | project.ext { 36 | artifactGroup = "com.jdiazcano.cfg4k" 37 | artifactVersion = "0.9.5" 38 | } 39 | 40 | repositories { 41 | mavenCentral() 42 | } 43 | 44 | subprojects { p -> 45 | apply plugin: 'java' 46 | apply plugin: 'kotlin' 47 | apply plugin: 'jacoco' 48 | apply plugin: 'org.junit.platform.gradle.plugin' 49 | apply plugin: "maven-publish" 50 | apply plugin: "com.jfrog.bintray" 51 | 52 | sourceCompatibility = 1.8 53 | 54 | repositories { 55 | mavenLocal() 56 | mavenCentral() 57 | jcenter() 58 | maven { url 'https://jetbrains.jfrog.io/jetbrains/spek-snapshots' } 59 | } 60 | 61 | bintray { 62 | user = 'jdiazcano' 63 | key = System.getenv("BINTRAY_KEY") 64 | publications = ["kotlinPublish"] 65 | //configurations = ["archives"] 66 | publish = true 67 | override = true 68 | pkg { 69 | repo = 'cfg4k' 70 | name = p.name 71 | userOrg = 'jdiazcano' 72 | licenses = ['Apache-2.0'] 73 | vcsUrl = 'https://github.com/jdiazcano/cfg4k.git' 74 | publicDownloadNumbers = true 75 | version { 76 | name = project.artifactVersion 77 | desc = 'Cfg4k is a configuration library made for Kotlin in Kotlin!' 78 | released = new Date() 79 | vcsTag = project.artifactVersion 80 | } 81 | } 82 | } 83 | 84 | // custom tasks for creating source/javadoc jars 85 | task sourcesJar(type: Jar, dependsOn: classes) { 86 | classifier = 'sources' 87 | from sourceSets.main.allSource 88 | } 89 | 90 | task javadocJar(type: Jar, dependsOn: javadoc) { 91 | classifier = 'javadoc' 92 | from javadoc.destinationDir 93 | } 94 | 95 | // add javadoc/source jar tasks as artifacts 96 | artifacts { 97 | archives sourcesJar, javadocJar 98 | } 99 | 100 | publishing { 101 | publications { 102 | kotlinPublish(MavenPublication) { 103 | from components.java 104 | groupId 'com.jdiazcano.cfg4k' 105 | artifactId p.name 106 | version project.artifactVersion 107 | 108 | artifact sourcesJar 109 | artifact javadocJar 110 | } 111 | } 112 | } 113 | 114 | // Needed because there's something messy with kotlin version numbers and dependencies 115 | configurations.all { 116 | resolutionStrategy { 117 | eachDependency { DependencyResolveDetails details -> 118 | if (details.requested.group == 'org.jetbrains.kotlin') { 119 | details.useVersion "$versions.jetbrains.kotlin" 120 | } 121 | } 122 | } 123 | } 124 | } 125 | 126 | def activeSubprojects = subprojects.findAll { it.name.startsWith("cfg4k") } 127 | def subprojectsExecFiles = files( activeSubprojects.collect { "${it.name}/build/jacoco/junitPlatformTest.exec" } ) 128 | 129 | task publish(dependsOn: activeSubprojects.collect { it.tasks.findByPath("bintrayUpload") }) {} 130 | task publishLocal(dependsOn: activeSubprojects.collect { it.tasks.findByPath("publishToMavenLocal") }) {} 131 | -------------------------------------------------------------------------------- /cfg4k-bytebuddy/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: '../gradle/versions.gradle' 2 | 3 | dependencies { 4 | compile project(":cfg4k-core") 5 | compile libraries.jetbrains.kotlin.stdlib 6 | compile libraries.bytebuddy 7 | 8 | testCompile project(":cfg4k-json") 9 | testCompile libraries.jetbrains.spek.api 10 | testCompile libraries.junitrunner 11 | testCompile libraries.expekt 12 | testRuntime libraries.jetbrains.spek.engine 13 | } -------------------------------------------------------------------------------- /cfg4k-bytebuddy/src/test/kotlin/com/jdiazcano/cfg4k/bytebuddy/ByteBuddyConfigProviderReloadTest.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.bytebuddy 2 | 3 | import com.jdiazcano.cfg4k.loaders.PropertyConfigLoader 4 | import com.jdiazcano.cfg4k.providers.ConfigProvider 5 | import com.jdiazcano.cfg4k.providers.bind 6 | import com.jdiazcano.cfg4k.reloadstrategies.TimedReloadStrategy 7 | import com.jdiazcano.cfg4k.sources.FileConfigSource 8 | import com.winterbe.expekt.should 9 | import org.jetbrains.spek.api.Spek 10 | import org.jetbrains.spek.api.dsl.describe 11 | import org.jetbrains.spek.api.dsl.it 12 | import java.io.File 13 | import java.util.concurrent.TimeUnit 14 | 15 | class ByteBuddyConfigProviderReloadTest : Spek({ 16 | val text = """a=%reload1 17 | c=%reload2 18 | nested.a=reloaded nestedb 19 | """ 20 | describe("a timed reloadable bytebuddy proxy with json config loader") { 21 | it("bytebuddy test") { 22 | val file = File("timedreloadedfile.properties") 23 | file.createNewFile() 24 | file.writeText(text.replace("%reload1", "b").replace("%reload2", "d")) 25 | val provider = ByteBuddyConfigProvider(PropertyConfigLoader(FileConfigSource(file)), TimedReloadStrategy(1, TimeUnit.SECONDS)) 26 | checkProvider(file, provider, text) 27 | } 28 | 29 | } 30 | }) 31 | 32 | private fun checkProvider(file: File, provider: ConfigProvider, text: String, overriden: Boolean = false) { 33 | val bindedNormal = provider.bind("") 34 | bindedNormal.nested().a().should.be.equal("reloaded nestedb") 35 | 36 | val bindedProperty = provider.bind("") 37 | bindedProperty.nested.a().should.be.equal("reloaded nestedb") 38 | 39 | if (overriden) { 40 | bindedNormal.a().should.be.equal("overrideb") 41 | bindedProperty.a.should.be.equal("overrideb") 42 | } else { 43 | bindedNormal.a().should.be.equal("b") 44 | bindedProperty.a.should.be.equal("b") 45 | } 46 | bindedNormal.c().should.be.equal("d") 47 | bindedProperty.c.should.be.equal("d") 48 | 49 | var lastReload = 1 50 | val lastIteration = 3 51 | for (i in 1..5) { 52 | if (i > lastIteration) { 53 | provider.cancelReload() 54 | lastReload = lastIteration // This is the last reload iteration (8-1) 55 | } 56 | if (overriden) { 57 | file.writeText(text.replace("%reload1", "overrideb$lastReload").replace("c=%reload2\n", "")) 58 | } else { 59 | file.writeText(text.replace("%reload1", "b$lastReload").replace("%reload2", "d$lastReload")) 60 | } 61 | Thread.sleep(1500) 62 | if (overriden) { 63 | bindedNormal.a().should.be.equal("overrideb$lastReload") 64 | bindedNormal.c().should.be.equal("d") 65 | } else { 66 | bindedNormal.a().should.be.equal("b$lastReload") 67 | bindedNormal.c().should.be.equal("d$lastReload") 68 | } 69 | lastReload++ 70 | } 71 | file.delete() 72 | } 73 | 74 | interface Nested { 75 | fun a(): String 76 | } 77 | 78 | interface Normal { 79 | fun nested(): Nested 80 | fun a(): String 81 | fun c(): String 82 | } 83 | 84 | interface Properties { 85 | val nested: Nested 86 | val a: String 87 | val c: String 88 | } -------------------------------------------------------------------------------- /cfg4k-bytebuddy/src/test/kotlin/com/jdiazcano/cfg4k/bytebuddy/ByteBuddyConfigProviderTest.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.bytebuddy 2 | 3 | import com.jdiazcano.cfg4k.core.toConfig 4 | import com.jdiazcano.cfg4k.json.JsonConfigLoader 5 | import com.jdiazcano.cfg4k.loaders.PropertyConfigLoader 6 | import com.jdiazcano.cfg4k.providers.bind 7 | import com.jdiazcano.cfg4k.providers.get 8 | import com.jdiazcano.cfg4k.sources.StringConfigSource 9 | import com.jdiazcano.cfg4k.sources.URLConfigSource 10 | import com.winterbe.expekt.should 11 | import org.jetbrains.spek.api.Spek 12 | import org.jetbrains.spek.api.dsl.describe 13 | import org.jetbrains.spek.api.dsl.it 14 | 15 | class ByteBuddyConfigProviderTest : Spek({ 16 | describe("a bytebuddyconfigprovider") { 17 | val provider = ByteBuddyConfigProvider( 18 | JsonConfigLoader(URLConfigSource(javaClass.classLoader.getResource("test.json"))) 19 | ) 20 | 21 | it("integer properties") { 22 | provider.get("integerProperty").should.be.equal(1) 23 | } 24 | 25 | it("long properties") { 26 | provider.get("longProperty").should.be.equal(2) 27 | } 28 | 29 | it("short properties") { 30 | provider.get("shortProperty").should.be.equal(1) 31 | } 32 | 33 | it("float properties") { 34 | provider.get("floatProperty").should.be.equal(2.1F) 35 | } 36 | 37 | it("double properties") { 38 | provider.get("doubleProperty").should.be.equal(1.1) 39 | } 40 | 41 | it("byte properties") { 42 | provider.get("byteProperty").should.be.equal(2) 43 | } 44 | 45 | it("boolean properties") { 46 | provider.get("booleanProperty").should.be.`true` 47 | } 48 | 49 | describe("a binded interface") { 50 | val testBinder = provider.bind("") 51 | 52 | it("binding test") { 53 | 54 | testBinder.nullProperty().should.be.`null` 55 | testBinder.booleanProperty().should.be.`true` 56 | testBinder.integerProperty().should.be.equal(1) 57 | testBinder.longProperty().should.be.equal(2) 58 | testBinder.shortProperty().should.be.equal(1) 59 | testBinder.floatProperty().should.be.equal(2.1F) 60 | testBinder.doubleProperty().should.be.equal(1.1) 61 | testBinder.byteProperty().should.be.equal(2) 62 | testBinder.a().should.be.equal("b") 63 | testBinder.c().should.be.equal("d") 64 | testBinder.listOfLists.should.be.equal(listOf(listOf(1, 2), listOf(3, 4))) 65 | } 66 | 67 | it("can retrieve a map") { 68 | testBinder.myCoolMap.should.be.equal(mapOf("one" to 1, "two" to 2)) 69 | } 70 | 71 | it("can retrieve a map with maps and lists") { 72 | testBinder.myCoolComplexMap.should.be.equal(mapOf( 73 | "one" to mapOf(1 to listOf("one", "uno"), 10 to listOf("diez", "ten")), 74 | "two" to mapOf(2 to listOf("dos", "two"), 20 to listOf("veinte", "twenty")))) 75 | } 76 | } 77 | 78 | it("foreach binding test to MANUALLY detect if there are memory leaks") { 79 | (1..10).forEach { 80 | val testBinder = provider.bind("") 81 | val otherTestBinder = provider.bind("") 82 | testBinder.equals(otherTestBinder).should.be.`false` // they must be different instances since classes are different 83 | testBinder.nullProperty().should.be.`null` 84 | } 85 | } 86 | 87 | } 88 | 89 | describe("a simple bytebuddy provider") { 90 | val provider = ByteBuddyConfigProvider(PropertyConfigLoader(StringConfigSource(""" 91 | a=b 92 | nested.a=b 93 | """))) 94 | val obj = "b".toConfig() 95 | val nestedObj = mapOf("a" to "b").toConfig() 96 | 97 | it("has the correct toString") { 98 | provider.load("a").toString().should.be.equal("ConfigObject(value=b)") 99 | } 100 | 101 | it("a primitive is equal to the expected ConfigObject") { 102 | provider.load("a").should.be.equal(obj) 103 | } 104 | 105 | it("a binding is equal to the expected ConfigObject") { 106 | provider.bind("nested").toString().should.be.equal(nestedObj.toString()) 107 | } 108 | } 109 | }) 110 | -------------------------------------------------------------------------------- /cfg4k-bytebuddy/src/test/kotlin/com/jdiazcano/cfg4k/bytebuddy/NullsConfigProviderTest.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.bytebuddy 2 | 3 | import com.jdiazcano.cfg4k.core.toConfig 4 | import com.jdiazcano.cfg4k.loaders.PropertyConfigLoader 5 | import com.jdiazcano.cfg4k.providers.DefaultConfigProvider 6 | import com.jdiazcano.cfg4k.providers.OverrideConfigProvider 7 | import com.jdiazcano.cfg4k.providers.Providers 8 | import com.jdiazcano.cfg4k.providers.bind 9 | import com.jdiazcano.cfg4k.providers.cache 10 | import com.jdiazcano.cfg4k.providers.get 11 | import com.jdiazcano.cfg4k.providers.getOrNull 12 | import com.jdiazcano.cfg4k.sources.URLConfigSource 13 | import com.jdiazcano.cfg4k.utils.SettingNotFound 14 | import com.winterbe.expekt.should 15 | import org.jetbrains.spek.api.Spek 16 | import org.jetbrains.spek.api.dsl.describe 17 | import org.jetbrains.spek.api.dsl.it 18 | import kotlin.test.assertFailsWith 19 | 20 | class NullsConfigProviderTest : Spek({ 21 | 22 | val loader = PropertyConfigLoader(URLConfigSource(javaClass.classLoader.getResource("nulltest.properties"))) 23 | val providers = listOf( 24 | Providers.bytebuddy(loader), 25 | Providers.bytebuddy(loader).cache() 26 | ) 27 | val overridingLoader = PropertyConfigLoader(URLConfigSource(javaClass.classLoader.getResource("overridingnulltest.properties"))) 28 | val overridingProvider = OverrideConfigProvider(DefaultConfigProvider(overridingLoader), providers[0]) 29 | 30 | providers.forEachIndexed { i, provider -> 31 | describe("provider[$i]") { 32 | it("default values") { 33 | assertFailsWith { 34 | provider.bind("valueNonNullableWithoutDefault").integerProperty 35 | } 36 | 37 | provider.bind("valueNonNullable").integerProperty.should.be.equal(1) 38 | provider.bind("valueNullableWithDefault").doesntExist.should.be.equal(123456) 39 | provider.bind("valueNullableWithoutDefault").doesntExist.should.be.`null` 40 | 41 | provider.get("valueNonNullable.integerProperty").should.be.equal(1) 42 | provider.get("doesntExist1", 123456).should.be.equal(123456) 43 | provider.getOrNull("doesntExist2").should.be.`null` 44 | provider.getOrNull("doesntExist3").should.be.`null` 45 | provider.getOrNull("doesntExist4", 123456).should.be.equal(123456) 46 | provider.getOrNull("doesntExist5", 123456).should.be.equal(123456) 47 | } 48 | } 49 | } 50 | 51 | describe("an overrided config provider") { 52 | it("the overrided values should be ok") { 53 | assertFailsWith { 54 | overridingProvider.bind("valueNonNullableWithoutDefault").integerProperty 55 | } 56 | 57 | overridingProvider.bind("valueNonNullable").integerProperty.should.be.equal(2) 58 | overridingProvider.bind("valueNullableWithDefault").doesntExist.should.be.equal(123456) 59 | overridingProvider.bind("valueNullableWithoutDefault").doesntExist.should.be.`null` 60 | 61 | overridingProvider.get("valueNonNullable.integerProperty").should.be.equal(2) 62 | overridingProvider.get("doesntExist1", 123456).should.be.equal(123456) 63 | overridingProvider.getOrNull("doesntExist2").should.be.`null` 64 | overridingProvider.getOrNull("doesntExist3").should.be.`null` 65 | overridingProvider.getOrNull("doesntExist4", 123456).should.be.equal(123456) 66 | overridingProvider.getOrNull("doesntExist5", 123456).should.be.equal(123456) 67 | } 68 | } 69 | 70 | describe("test the loader") { 71 | it ("should have cool properties") { 72 | loader.get("valueNonNullable.integerProperty").should.be.equal("1".toConfig()) 73 | loader.get("doesntExist2").should.be.`null` 74 | } 75 | } 76 | }) 77 | 78 | interface ValueNonNullableWithDefault { 79 | val integerProperty: Int get() = 123456 80 | } 81 | 82 | interface ValueNonNullableWithoutDefault { 83 | val integerProperty: Int 84 | } 85 | 86 | interface ValueNullableWithDefault { 87 | val doesntExist: Int? get() = 123456 88 | } 89 | 90 | interface ValueNullableWithoutDefault { 91 | val doesntExist: Int? 92 | } 93 | -------------------------------------------------------------------------------- /cfg4k-bytebuddy/src/test/kotlin/com/jdiazcano/cfg4k/bytebuddy/TestBinder.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.bytebuddy 2 | 3 | interface TestBinder { 4 | fun nullProperty(): Int? 5 | fun integerProperty(): Int 6 | fun a(): String 7 | fun c(): String 8 | fun booleanProperty(): Boolean 9 | fun longProperty(): Long 10 | fun shortProperty(): Short 11 | fun doubleProperty(): Double 12 | fun floatProperty(): Float 13 | fun byteProperty(): Byte 14 | fun list(): List 15 | fun floatList(): List 16 | val listOfLists: List> 17 | val myCoolMap: Map 18 | val myCoolComplexMap: Map>> 19 | } -------------------------------------------------------------------------------- /cfg4k-bytebuddy/src/test/kotlin/com/jdiazcano/cfg4k/bytebuddy/subpackage/Binder.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.bytebuddy.subpackage 2 | 3 | interface TestBinder { 4 | fun nullProperty(): Int? 5 | fun integerProperty(): Int 6 | fun a(): String 7 | fun c(): String 8 | fun booleanProperty(): Boolean 9 | fun longProperty(): Long 10 | fun shortProperty(): Short 11 | fun doubleProperty(): Double 12 | fun floatProperty(): Float 13 | fun byteProperty(): Byte 14 | fun list(): List 15 | fun floatList(): List 16 | } -------------------------------------------------------------------------------- /cfg4k-bytebuddy/src/test/resources/nulltest.properties: -------------------------------------------------------------------------------- 1 | valueNonNullable.integerProperty=1 -------------------------------------------------------------------------------- /cfg4k-bytebuddy/src/test/resources/overridingnulltest.properties: -------------------------------------------------------------------------------- 1 | valueNonNullable.integerProperty=2 -------------------------------------------------------------------------------- /cfg4k-bytebuddy/src/test/resources/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": "b", 3 | "c": "d", 4 | "integerProperty": 1, 5 | "longProperty": 2, 6 | "shortProperty": 1, 7 | "doubleProperty": 1.1, 8 | "floatProperty": 2.1, 9 | "byteProperty": 2, 10 | "list": [ 11 | 1, 12 | 2, 13 | 3, 14 | 4, 15 | 5, 16 | 6, 17 | 7 18 | ], 19 | "floatList": [ 20 | 1.2, 21 | 2.2, 22 | 3.2 23 | ], 24 | "complexList": [ 25 | { 26 | "wow": "such0", 27 | "doge": 0 28 | }, 29 | { 30 | "wow": "such1", 31 | "doge": 1 32 | } 33 | ], 34 | "complexSet": [ 35 | { 36 | "wow": "such0", 37 | "doge": 0 38 | }, 39 | { 40 | "wow": "such1", 41 | "doge": 1 42 | } 43 | ], 44 | "booleanProperty": true, 45 | "bigIntegerProperty": 1, 46 | "bigDecimalProperty": 1.1, 47 | "dateProperty": "01-01-2017", 48 | "calendarProperty": "01-01-2017", 49 | "localDateProperty": "01-01-2017", 50 | "isoLocalDateProperty": "2017-01-01", 51 | "localDateTimeProperty": "01-01-2017 18:01:31", 52 | "isoLocalDateTimeProperty": "2017-01-01T18:01:31", 53 | "zonedDateTimeProperty": "01-01-2017 18:01:31", 54 | "isoZonedDateTimeProperty": "2017-01-01T18:01:31+01:00", 55 | "offsetDateTimeProperty": "01-01-2017 18:01:31+01:00", 56 | "isoOffsetDateTimeProperty": "2017-01-01T18:01:31+01:00", 57 | "offsetTimeProperty": "18:01:31+01:00", 58 | "isoOffsetTimeProperty": "18:01:31+01:00", 59 | "nested": { 60 | "a": "nestedb" 61 | }, 62 | "betterIntList": [ 63 | 1, 64 | 2, 65 | 100 66 | ], 67 | "betterStringList": [ 68 | "a", 69 | "b", 70 | "c" 71 | ], 72 | "betterEnumList": [ 73 | "A", 74 | "B" 75 | ], 76 | "enumList": [ 77 | "A", 78 | "B" 79 | ], 80 | "url": "https://www.amazon.com", 81 | "uri": "https://www.amazon.com", 82 | "path": "mypath.txt", 83 | "file": "myfile.txt", 84 | "toString": "this should not be ever used", 85 | "listOfLists": [ 86 | [ 87 | 1, 88 | 2 89 | ], 90 | [ 91 | 3, 92 | 4 93 | ] 94 | ], 95 | "myCoolMap": { 96 | "one": 1, 97 | "two": 2 98 | }, 99 | "myCoolComplexMap": { 100 | "one": { 101 | "1": [ 102 | "one", 103 | "uno" 104 | ], 105 | "10": [ 106 | "diez", 107 | "ten" 108 | ] 109 | }, 110 | "two": { 111 | "2": [ 112 | "dos", 113 | "two" 114 | ], 115 | "20": [ 116 | "veinte", 117 | "twenty" 118 | ] 119 | } 120 | } 121 | } -------------------------------------------------------------------------------- /cfg4k-bytebuddy/src/test/resources/test.properties: -------------------------------------------------------------------------------- 1 | a=b 2 | c=d 3 | integerProperty=1 4 | longProperty=2 5 | shortProperty=1 6 | doubleProperty=1.1 7 | floatProperty=2.1 8 | byteProperty=2 9 | booleanProperty=true 10 | enumList=A,B 11 | toString=this should not be ever used -------------------------------------------------------------------------------- /cfg4k-cli/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: '../gradle/versions.gradle' 2 | 3 | dependencies { 4 | compile project(":cfg4k-core") 5 | compile libraries.jetbrains.kotlin.stdlib 6 | compile libraries.jcommander 7 | 8 | testCompile libraries.jetbrains.spek.api 9 | testCompile libraries.junitrunner 10 | testCompile libraries.expekt 11 | testRuntime libraries.jetbrains.spek.engine 12 | } -------------------------------------------------------------------------------- /cfg4k-cli/src/main/kotlin/com/jdiazcano/cfg4k/cli/CommandLineLoader.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.cli 2 | 3 | import com.beust.jcommander.DynamicParameter 4 | import com.beust.jcommander.JCommander 5 | import com.jdiazcano.cfg4k.core.toConfig 6 | import com.jdiazcano.cfg4k.loaders.DefaultConfigLoader 7 | import java.util.HashMap 8 | 9 | class CommandLineLoader(args: Array) : DefaultConfigLoader() { 10 | 11 | init { 12 | val parsed = JCommander.newBuilder().args(args).addObject(Args()).build().objects[0] as Args 13 | root = parsed.params.toConfig() 14 | } 15 | 16 | override fun reload() {} 17 | } 18 | 19 | private open class Args { 20 | @DynamicParameter(names = arrayOf("-")) 21 | var params = HashMap() 22 | } -------------------------------------------------------------------------------- /cfg4k-cli/src/test/kotlin/com/jdiazcano/cfg4k/cli/CommandLineLoaderTest.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.cli 2 | 3 | import com.jdiazcano.cfg4k.core.toConfig 4 | import com.winterbe.expekt.should 5 | import org.jetbrains.spek.api.Spek 6 | import org.jetbrains.spek.api.dsl.describe 7 | 8 | class CommandLineLoaderTest : Spek({ 9 | val tests = hashMapOf( 10 | arrayOf("-test=1", "-number=100", "-whatever=doge") to hashMapOf( 11 | "test" to 1.toConfig(), 12 | "number" to 100.toConfig(), 13 | "whatever" to "doge".toConfig() 14 | ).toConfig() 15 | ) 16 | describe("a command line loader") { 17 | tests.forEach { key, value -> 18 | val loader = CommandLineLoader(key) 19 | loader.get("").should.be.equal(value) 20 | } 21 | } 22 | }) -------------------------------------------------------------------------------- /cfg4k-core/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: '../gradle/versions.gradle' 2 | 3 | dependencies { 4 | compile libraries.jetbrains.kotlin.stdlib 5 | compile libraries.jetbrains.kotlin.test 6 | compile libraries.jetbrains.kotlin.reflect 7 | compile libraries.logging 8 | 9 | testCompile libraries.kotlintest 10 | testCompile libraries.mockk 11 | testCompile libraries.mockwebserver 12 | testCompile project(":cfg4k-json") 13 | testCompile project(":cfg4k-yaml") 14 | testCompile project(":cfg4k-hocon") 15 | testCompile project(":cfg4k-cli") 16 | testCompile project(":cfg4k-bytebuddy") 17 | } -------------------------------------------------------------------------------- /cfg4k-core/src/main/kotlin/com/jdiazcano/cfg4k/binders/Binder.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.binders 2 | 3 | import com.jdiazcano.cfg4k.providers.ConfigProvider 4 | 5 | /** 6 | * Interface that defines the method for binding an interface for a configuration. 7 | */ 8 | interface Binder { 9 | 10 | /** 11 | * Binds an interface 12 | * 13 | * This method will return an implementation of the interface with the given methods for configuration. 14 | * 15 | * @param prefix The prefix of the configuration, if this is not empty, configs starting with the prefix will be used 16 | * @param type The interface that will be implemented and it will be returned 17 | */ 18 | fun bind(configProvider: ConfigProvider, prefix: String, type: Class): T 19 | } -------------------------------------------------------------------------------- /cfg4k-core/src/main/kotlin/com/jdiazcano/cfg4k/binders/Converters.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.binders 2 | 3 | import com.jdiazcano.cfg4k.core.ConfigContext 4 | import com.jdiazcano.cfg4k.core.ConfigObject 5 | import com.jdiazcano.cfg4k.parsers.ListParser 6 | import com.jdiazcano.cfg4k.parsers.MapParser 7 | import com.jdiazcano.cfg4k.parsers.Parsers.findParser 8 | import com.jdiazcano.cfg4k.parsers.Parsers.isParseable 9 | import com.jdiazcano.cfg4k.utils.ParserClassNotFound 10 | import com.jdiazcano.cfg4k.utils.TypeStructure 11 | 12 | 13 | fun convert(context: ConfigContext, configObject: ConfigObject, structure: TypeStructure): Any? { 14 | return convertBase(context, configObject, structure) { 15 | context.bind(context.propertyName, structure.raw) 16 | } 17 | } 18 | 19 | fun convertGet(context: ConfigContext, configObject: ConfigObject, structure: TypeStructure): Any? { 20 | return convertBase(context, configObject, structure) { 21 | context.get(context.propertyName, structure.raw) 22 | } 23 | } 24 | 25 | fun convertGetOrNull(context: ConfigContext, configObject: ConfigObject, structure: TypeStructure): Any? { 26 | return convertBase(context, configObject, structure) { 27 | context.getOrNull(context.propertyName, structure.raw) 28 | } 29 | } 30 | 31 | fun convertBase(context: ConfigContext, configObject: ConfigObject, structure: TypeStructure, getter: () -> Any?): Any? { 32 | return when { 33 | structure.isMap() -> MapParser.parse(context, configObject, structure) 34 | configObject.isList() -> ListParser.parse(context, configObject, structure) 35 | structure.raw.isParseable() -> structure.raw.findParser().parse(context, configObject, structure) 36 | structure.raw.isInterface -> getter() 37 | structure.raw.kotlin.isData -> DataClassBinder.bind(context.provider, context.propertyName, structure.raw.kotlin) 38 | else -> throw ParserClassNotFound("Couldn't parse class ${structure.raw}") 39 | } 40 | } -------------------------------------------------------------------------------- /cfg4k-core/src/main/kotlin/com/jdiazcano/cfg4k/binders/DataClassBinder.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.binders 2 | 3 | import com.jdiazcano.cfg4k.core.ConfigContext 4 | import com.jdiazcano.cfg4k.providers.ConfigProvider 5 | import com.jdiazcano.cfg4k.providers.load 6 | import com.jdiazcano.cfg4k.utils.ConstructorNotFound 7 | import com.jdiazcano.cfg4k.utils.convert 8 | import kotlin.reflect.KClass 9 | import kotlin.reflect.KVisibility 10 | import kotlin.reflect.jvm.javaType 11 | 12 | object DataClassBinder { 13 | fun bind(configProvider: ConfigProvider, prefix: String, type: KClass): T { 14 | if (type.visibility == KVisibility.PRIVATE) { 15 | throw IllegalArgumentException("Binding data classes can't be private: ${type.qualifiedName}") 16 | } 17 | 18 | val constructors = type.constructors 19 | val configObject = configProvider.load(prefix) ?: throw IllegalArgumentException("Config object not found: $prefix") 20 | 21 | val paramNames = configObject.asObject().map { it.key } 22 | val matchingConstructor = constructors 23 | .firstOrNull { c -> c.parameters.filterNot { it.isOptional }.map { param -> param.name }.containsAll(paramNames) } 24 | ?: throw ConstructorNotFound("Constructor for class ${type.simpleName} wasn't found. Names: $paramNames") 25 | 26 | val constructorParameters = matchingConstructor.parameters.map { parameter -> 27 | val structure = parameter.type.javaType.convert() 28 | val context = ConfigContext(configProvider, concatPrefix(prefix, parameter.name!!)) 29 | val subObject = configProvider.load(context) ?: if (parameter.type.isMarkedNullable) null else throw IllegalArgumentException("Parameter ${parameter.name} isn't marked as nullable and avlue was null.") 30 | val value = subObject?.let { convert(context, subObject, structure) } 31 | 32 | parameter to value 33 | }.toMap() 34 | 35 | return matchingConstructor.callBy(constructorParameters) 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /cfg4k-core/src/main/kotlin/com/jdiazcano/cfg4k/binders/ProxyBinder.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.binders 2 | 3 | import com.jdiazcano.cfg4k.providers.ConfigProvider 4 | import java.lang.reflect.Proxy 5 | 6 | @Suppress("UNCHECKED_CAST") 7 | class ProxyBinder : Binder { 8 | override fun bind(configProvider: ConfigProvider, prefix: String, type: Class): T { 9 | val handler = BindingInvocationHandler(configProvider, prefix) 10 | return Proxy.newProxyInstance(type.classLoader, arrayOf(type), handler) as T 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /cfg4k-core/src/main/kotlin/com/jdiazcano/cfg4k/loaders/ConfigLoader.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 Javier Díaz-Cano Martín-Albo (javierdiazcanom@gmail.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jdiazcano.cfg4k.loaders 18 | 19 | import com.jdiazcano.cfg4k.core.ConfigObject 20 | 21 | /** 22 | * A config loader only defines a get method which will return a string with the value 23 | */ 24 | interface ConfigLoader { 25 | 26 | /** 27 | * Searches the value from a key 28 | * 29 | * @param key The key of the value that we are looking for 30 | * @return The value of the key 31 | */ 32 | fun get(key: String): ConfigObject? 33 | 34 | fun reload() 35 | } 36 | -------------------------------------------------------------------------------- /cfg4k-core/src/main/kotlin/com/jdiazcano/cfg4k/loaders/DefaultConfigLoader.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.loaders 2 | 3 | import com.jdiazcano.cfg4k.core.ConfigObject 4 | import com.jdiazcano.cfg4k.core.toConfig 5 | 6 | fun DefaultConfigLoader(map: Map) = DefaultConfigLoader(map.toConfig()) 7 | 8 | fun DefaultConfigLoader(vararg pairs: Pair) = DefaultConfigLoader(mapOf(*pairs).toConfig()) 9 | 10 | open class DefaultConfigLoader(protected var root: ConfigObject = "".toConfig()) : ConfigLoader { 11 | override fun get(key: String): ConfigObject? { 12 | if (key == "") { 13 | return root 14 | } 15 | 16 | val split = key.split('.') 17 | val last = split.last() 18 | 19 | if (split.size == 1) { 20 | return root.child(last) 21 | } else { 22 | var root: ConfigObject? = root 23 | for (index in 0..split.size - 2) { 24 | if (root == null) { 25 | return null 26 | } else if (root.isString()) { 27 | throw IllegalArgumentException("Trying to get a key from a primitive") 28 | } 29 | 30 | root = root.child(split[index]) 31 | } 32 | 33 | return root?.child(last) 34 | } 35 | } 36 | 37 | fun merge(loader: DefaultConfigLoader): ConfigLoader { 38 | return DefaultConfigLoader(loader.root.merge(root)) 39 | } 40 | 41 | override fun reload() {} 42 | 43 | } 44 | 45 | internal val numberRegex = "([^\\[]+)?(?:\\[(\\d+)])".toRegex() 46 | 47 | internal fun findNumbers(key: String): Pair { 48 | val result = numberRegex.find(key) 49 | return if (result != null) { 50 | result.groups[2]?.value?.toInt() to (result.groups[1]?.value ?: "") 51 | } else { 52 | null to key 53 | } 54 | } -------------------------------------------------------------------------------- /cfg4k-core/src/main/kotlin/com/jdiazcano/cfg4k/loaders/EnvironmentConfigLoader.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.loaders 2 | 3 | import com.jdiazcano.cfg4k.core.ConfigObject 4 | 5 | private val DEFAULT_TRANSFORMERS = mutableListOf<(String) -> String>( 6 | { key -> key.replace('_', '.') }, 7 | { key -> key.replace('-', '.') } 8 | ) 9 | 10 | /** 11 | * EnvironmentConfigLoader will try to match the key to an environment variable. This will apply a series of 12 | * transformations before matching. 13 | */ 14 | open class EnvironmentConfigLoader( 15 | protected val transformations: MutableList<(String) -> String> = DEFAULT_TRANSFORMERS 16 | ) : DefaultConfigLoader(System.getenv().transformice(transformations).toProperties().toConfig()) { 17 | 18 | override fun get(key: String): ConfigObject? { 19 | transformations.forEach { 20 | val transformed = it(key).toUpperCase() 21 | val value = super.get(transformed) 22 | if (value != null) { 23 | return value 24 | } 25 | } 26 | 27 | return null 28 | } 29 | 30 | override fun reload() { 31 | root = System.getenv().transformice(transformations).toProperties().toConfig() 32 | } 33 | } 34 | 35 | private fun Map.transformice(transformations: MutableList<(String) -> String>) = map { (key, value) -> 36 | transformations.fold(key) { transformedKey, transformer -> transformer(transformedKey) } to value 37 | }.toMap() -------------------------------------------------------------------------------- /cfg4k-core/src/main/kotlin/com/jdiazcano/cfg4k/loaders/PropertyConfigLoader.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 Javier Díaz-Cano Martín-Albo (javierdiazcanom@gmail.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jdiazcano.cfg4k.loaders 18 | 19 | import com.jdiazcano.cfg4k.core.ConfigObject 20 | import com.jdiazcano.cfg4k.core.toConfig 21 | import com.jdiazcano.cfg4k.sources.ConfigSource 22 | import mu.KotlinLogging 23 | import java.util.* 24 | import kotlin.collections.ArrayList 25 | 26 | private val logger = KotlinLogging.logger {} 27 | 28 | open class PropertyConfigLoader( 29 | private val source: ConfigSource 30 | ) : DefaultConfigLoader() { 31 | 32 | init { 33 | loadProperties() 34 | } 35 | 36 | private fun loadProperties() { 37 | root = Properties().apply { load(source.read()) }.toConfig() 38 | } 39 | 40 | override fun reload() { 41 | loadProperties() 42 | } 43 | 44 | } 45 | 46 | fun Properties.toConfig(): ConfigObject { 47 | val map = mutableMapOf() 48 | map { (key, value) -> 49 | val keyAsString = key.toString() 50 | val keys = keyAsString.split('.') 51 | 52 | if (keys.size == 1) { 53 | val (number, cleanKey) = findNumbers(keyAsString) 54 | 55 | if (number == null) { 56 | if (map[cleanKey] != null) { 57 | logger.warn { "$key will be overridden as it is defined twice." } 58 | } 59 | 60 | map[cleanKey] = value 61 | } else { 62 | val list = map.getOrPut(cleanKey) { arrayListOf() } as MutableList 63 | list += value 64 | } 65 | } else { 66 | val parentValue = keys.dropLast(1).fold(map as Any) { m, k -> 67 | m as MutableMap 68 | 69 | val (number, cleanKey) = findNumbers(k) 70 | val innerValue = m.getOrPut(cleanKey) { 71 | if (number == null) { 72 | mutableMapOf() 73 | } else { 74 | arrayListOf(mutableMapOf()) 75 | } 76 | } 77 | 78 | if (number == null) { 79 | if (innerValue is String) { 80 | logger.warn { "Key ($key=$innerValue) has overridden another value used as root" } 81 | mutableMapOf() 82 | } else { 83 | innerValue 84 | } 85 | } else { 86 | innerValue as ArrayList 87 | (innerValue.size..number).forEach { _ -> 88 | innerValue.add(mutableMapOf()) 89 | } 90 | innerValue[number] 91 | } 92 | } 93 | 94 | setValue(parentValue, keys, value) 95 | } 96 | } 97 | 98 | return map.toConfig() 99 | } 100 | 101 | private fun setValue(parentValue: Any, keys: List, value: Any) { 102 | val (number, key) = findNumbers(keys.last()) 103 | parentValue as MutableMap 104 | if (number == null) { 105 | if (parentValue[key] == null) { 106 | parentValue[key] = value 107 | } else { 108 | logger.warn { "Key '${keys.joinToString(".")}' was ignored as it was going to override another value." } 109 | } 110 | } else { 111 | val list = parentValue.getOrPut(key) { mutableListOf() } as MutableList 112 | list.add(value) 113 | } 114 | } -------------------------------------------------------------------------------- /cfg4k-core/src/main/kotlin/com/jdiazcano/cfg4k/loaders/SystemPropertyConfigLoader.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.loaders 2 | 3 | open class SystemPropertyConfigLoader : DefaultConfigLoader(System.getProperties().toConfig()) { 4 | override fun reload() { 5 | root = System.getProperties().toConfig() 6 | } 7 | } 8 | 9 | -------------------------------------------------------------------------------- /cfg4k-core/src/main/kotlin/com/jdiazcano/cfg4k/parsers/BigParsers.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.parsers 2 | 3 | import com.jdiazcano.cfg4k.core.ConfigContext 4 | import com.jdiazcano.cfg4k.core.ConfigObject 5 | import com.jdiazcano.cfg4k.utils.TypeStructure 6 | import java.math.BigDecimal 7 | import java.math.BigInteger 8 | 9 | object BigIntegerParser : Parser { 10 | override fun parse(context: ConfigContext, value: ConfigObject, typeStructure: TypeStructure) = BigInteger(value.asString()) 11 | } 12 | 13 | object BigDecimalParser : Parser { 14 | override fun parse(context: ConfigContext, value: ConfigObject, typeStructure: TypeStructure) = BigDecimal(value.asString()) 15 | } -------------------------------------------------------------------------------- /cfg4k-core/src/main/kotlin/com/jdiazcano/cfg4k/parsers/CommonParsers.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.parsers 2 | 3 | import com.jdiazcano.cfg4k.binders.convert 4 | import com.jdiazcano.cfg4k.binders.createCollection 5 | import com.jdiazcano.cfg4k.core.ConfigContext 6 | import com.jdiazcano.cfg4k.core.ConfigObject 7 | import com.jdiazcano.cfg4k.core.toConfig 8 | import com.jdiazcano.cfg4k.utils.TypeStructure 9 | import java.net.InetAddress 10 | import java.sql.Connection 11 | import java.sql.Driver 12 | import java.sql.DriverManager 13 | import java.util.UUID 14 | import java.util.regex.Pattern 15 | 16 | object PatternParser : Parser { 17 | override fun parse(context: ConfigContext, value: ConfigObject, typeStructure: TypeStructure) = Pattern.compile(value.asString()) 18 | } 19 | 20 | object RegexParser : Parser { 21 | override fun parse(context: ConfigContext, value: ConfigObject, typeStructure: TypeStructure) = value.asString().toRegex() 22 | } 23 | 24 | object UUIDParser : Parser { 25 | override fun parse(context: ConfigContext, value: ConfigObject, typeStructure: TypeStructure) = UUID.fromString(value.asString()) 26 | } 27 | 28 | object SQLDriverParser : Parser { 29 | override fun parse(context: ConfigContext, value: ConfigObject, typeStructure: TypeStructure) = DriverManager.getDriver(value.asString()) 30 | } 31 | 32 | object SQLConnectionParser : Parser { 33 | override fun parse(context: ConfigContext, value: ConfigObject, typeStructure: TypeStructure) = DriverManager.getConnection(value.asString()) 34 | } 35 | 36 | object InetAddressParser : Parser { 37 | override fun parse(context: ConfigContext, value: ConfigObject, typeStructure: TypeStructure) = InetAddress.getByName(value.asString()) 38 | } 39 | 40 | object ListParser : Parser> { 41 | override fun parse(context: ConfigContext, value: ConfigObject, typeStructure: TypeStructure): MutableCollection<*> { 42 | val collection = createCollection(typeStructure.raw) 43 | value.asList().forEachIndexed { index, innerObject -> 44 | collection.add(convert(context.copy(propertyName = "${context.propertyName}[$index]"), innerObject, typeStructure.generics[0])) 45 | } 46 | return collection 47 | } 48 | } 49 | 50 | object MapParser { 51 | fun parse(configContext: ConfigContext, value: ConfigObject, typeStructure: TypeStructure): Map<*, *> { 52 | return value.asObject().map { 53 | convert(configContext, it.key.toConfig(), typeStructure.generics[0]) to 54 | convert(configContext.copy(propertyName = "${configContext.propertyName}.${it.key}"), it.value, typeStructure.generics[1]) 55 | }.toMap() 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /cfg4k-core/src/main/kotlin/com/jdiazcano/cfg4k/parsers/EnumParser.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 Javier Díaz-Cano Martín-Albo (javierdiazcanom@gmail.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jdiazcano.cfg4k.parsers 18 | 19 | import com.jdiazcano.cfg4k.core.ConfigContext 20 | import com.jdiazcano.cfg4k.core.ConfigObject 21 | import com.jdiazcano.cfg4k.utils.TypeStructure 22 | 23 | class EnumParser> : Parser { 24 | override fun parse(context: ConfigContext, value: ConfigObject, typeStructure: TypeStructure): T { 25 | return java.lang.Enum.valueOf(typeStructure.type as Class, value.asString()) 26 | } 27 | } -------------------------------------------------------------------------------- /cfg4k-core/src/main/kotlin/com/jdiazcano/cfg4k/parsers/FileParsers.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.parsers 2 | 3 | import com.jdiazcano.cfg4k.core.ConfigContext 4 | import com.jdiazcano.cfg4k.core.ConfigObject 5 | import com.jdiazcano.cfg4k.utils.TypeStructure 6 | import java.io.File 7 | import java.net.URI 8 | import java.net.URL 9 | import java.nio.file.Path 10 | import java.nio.file.Paths 11 | 12 | object FileParser : Parser { 13 | override fun parse(context: ConfigContext, value: ConfigObject, typeStructure: TypeStructure) = File(value.asString()) 14 | } 15 | 16 | object PathParser : Parser { 17 | override fun parse(context: ConfigContext, value: ConfigObject, typeStructure: TypeStructure) = Paths.get(value.asString()) 18 | } 19 | 20 | object URIParser : Parser { 21 | override fun parse(context: ConfigContext, value: ConfigObject, typeStructure: TypeStructure) = URI(value.asString()) 22 | } 23 | 24 | object URLParser : Parser { 25 | override fun parse(context: ConfigContext, value: ConfigObject, typeStructure: TypeStructure) = URL(value.asString()) 26 | } -------------------------------------------------------------------------------- /cfg4k-core/src/main/kotlin/com/jdiazcano/cfg4k/parsers/Parser.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 Javier Díaz-Cano Martín-Albo (javierdiazcanom@gmail.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jdiazcano.cfg4k.parsers 18 | 19 | import com.jdiazcano.cfg4k.core.ConfigContext 20 | import com.jdiazcano.cfg4k.core.ConfigObject 21 | import com.jdiazcano.cfg4k.utils.TypeStructure 22 | 23 | /** 24 | * Base Parser interface, not all the implementations will use all the parameters but they will be there in case they 25 | * are needed. 26 | */ 27 | interface Parser { 28 | 29 | /** 30 | * Parses a string into a Type 31 | * 32 | * @param context The configuration context with the current provider 33 | * @param value The string that comes from the source 34 | * @param typeStructure The structure of the types and generic types 35 | */ 36 | fun parse(context: ConfigContext, value: ConfigObject, typeStructure: TypeStructure = TypeStructure(Any::class.java)): T 37 | } -------------------------------------------------------------------------------- /cfg4k-core/src/main/kotlin/com/jdiazcano/cfg4k/parsers/Parsers.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 Javier Díaz-Cano Martín-Albo (javierdiazcanom@gmail.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @file:Suppress("UNCHECKED_CAST") 18 | 19 | package com.jdiazcano.cfg4k.parsers 20 | 21 | import com.jdiazcano.cfg4k.utils.ParserClassNotFound 22 | import java.io.File 23 | import java.math.BigDecimal 24 | import java.math.BigInteger 25 | import java.net.InetAddress 26 | import java.net.URI 27 | import java.net.URL 28 | import java.nio.file.Path 29 | import java.sql.Connection 30 | import java.sql.Driver 31 | import java.util.UUID 32 | import java.util.regex.Pattern 33 | 34 | object Parsers { 35 | private val parsers: MutableMap, Parser> = mutableMapOf( 36 | Int::class.java to IntParser, 37 | Long::class.java to LongParser, 38 | Double::class.java to DoubleParser, 39 | Short::class.java to ShortParser, 40 | Float::class.java to FloatParser, 41 | Double::class.java to DoubleParser, 42 | Byte::class.java to ByteParser, 43 | String::class.java to StringParser, 44 | Boolean::class.java to BooleanParser, 45 | BigDecimal::class.java to BigDecimalParser, 46 | BigInteger::class.java to BigIntegerParser, 47 | Enum::class.java to EnumParser(), 48 | Class::class.java to ClassParser, 49 | File::class.java to FileParser, 50 | Path::class.java to PathParser, 51 | URL::class.java to URLParser, 52 | URI::class.java to URIParser, 53 | Regex::class.java to RegexParser, 54 | Pattern::class.java to PatternParser, 55 | UUID::class.java to UUIDParser, 56 | Driver::class.java to SQLDriverParser, 57 | Connection::class.java to SQLConnectionParser, 58 | InetAddress::class.java to InetAddressParser, 59 | 60 | /* These are needed for compatibility */ 61 | java.lang.Integer::class.java to IntParser, 62 | java.lang.Long::class.java to LongParser, 63 | java.lang.Double::class.java to DoubleParser, 64 | java.lang.Short::class.java to ShortParser, 65 | java.lang.Float::class.java to FloatParser, 66 | java.lang.Double::class.java to DoubleParser, 67 | java.lang.Byte::class.java to ByteParser, 68 | java.lang.String::class.java to StringParser, 69 | java.lang.Boolean::class.java to BooleanParser 70 | ) 71 | 72 | fun Class<*>.isParseable() = this in parsers || isEnum 73 | 74 | fun Class<*>.isExtendedParseable() = isParseable() || Collection::class.java.isAssignableFrom(this) || Map::class.java.isAssignableFrom(this) 75 | 76 | fun Class<*>.findParser(): Parser<*> { 77 | if (isEnum) { 78 | return parsers[Enum::class.java] as Parser<*> 79 | } else if (isParseable()) { 80 | return parsers[this] as Parser<*> 81 | } 82 | 83 | throw ParserClassNotFound("Parser not found for class $this") 84 | } 85 | 86 | fun addParser(type: Class, parser: Parser) { 87 | parsers[type] = parser 88 | } 89 | 90 | } 91 | 92 | -------------------------------------------------------------------------------- /cfg4k-core/src/main/kotlin/com/jdiazcano/cfg4k/parsers/PrimitiveParsers.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 Javier Díaz-Cano Martín-Albo (javierdiazcanom@gmail.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jdiazcano.cfg4k.parsers 18 | 19 | import com.jdiazcano.cfg4k.core.ConfigContext 20 | import com.jdiazcano.cfg4k.core.ConfigObject 21 | import com.jdiazcano.cfg4k.utils.TypeStructure 22 | 23 | object IntParser : Parser { 24 | override fun parse(context: ConfigContext, value: ConfigObject, typeStructure: TypeStructure) = value.asString().toInt() 25 | } 26 | 27 | object LongParser : Parser { 28 | override fun parse(context: ConfigContext, value: ConfigObject, typeStructure: TypeStructure) = value.asString().toLong() 29 | } 30 | 31 | object ShortParser : Parser { 32 | override fun parse(context: ConfigContext, value: ConfigObject, typeStructure: TypeStructure) = value.asString().toShort() 33 | } 34 | 35 | object BooleanParser : Parser { 36 | override fun parse(context: ConfigContext, value: ConfigObject, typeStructure: TypeStructure) = value.asString().toBoolean() 37 | } 38 | 39 | object FloatParser : Parser { 40 | override fun parse(context: ConfigContext, value: ConfigObject, typeStructure: TypeStructure) = value.asString().toFloat() 41 | } 42 | 43 | object DoubleParser : Parser { 44 | override fun parse(context: ConfigContext, value: ConfigObject, typeStructure: TypeStructure) = value.asString().toDouble() 45 | } 46 | 47 | object ByteParser : Parser { 48 | override fun parse(context: ConfigContext, value: ConfigObject, typeStructure: TypeStructure) = value.asString().toByte() 49 | } 50 | 51 | object StringParser : Parser { 52 | override fun parse(context: ConfigContext, value: ConfigObject, typeStructure: TypeStructure) = value.asString() 53 | } 54 | 55 | object ClassParser : Parser> { 56 | override fun parse(context: ConfigContext, value: ConfigObject, typeStructure: TypeStructure) = Class.forName(value.asString()) 57 | } -------------------------------------------------------------------------------- /cfg4k-core/src/main/kotlin/com/jdiazcano/cfg4k/providers/CachedConfigProvider.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 Javier Díaz-Cano Martín-Albo (javierdiazcanom@gmail.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jdiazcano.cfg4k.providers 18 | 19 | import java.lang.reflect.Type 20 | 21 | /** 22 | * This config provider will cache the calls so binding and property lookup is not done everytime. Reloading this 23 | * provider will clear the cache so things will have to be cached again. 24 | */ 25 | @Suppress("UNCHECKED_CAST") 26 | class CachedConfigProvider(private val configProvider: ConfigProvider) : ConfigProvider by configProvider { 27 | private val cache = mutableMapOf() 28 | 29 | init { 30 | configProvider.addReloadListener { cache.clear() } 31 | } 32 | 33 | override fun get(name: String, type: Type, default: T?): T { 34 | return if (cache.containsKey(name)) { 35 | cache[name] as T 36 | } else { 37 | val property: T = configProvider.get(name, type, default) 38 | cache[name] = property 39 | property 40 | } 41 | } 42 | 43 | override fun getOrNull(name: String, type: Type, default: T?): T? { 44 | return if (cache.containsKey(name)) { 45 | cache[name] as T? 46 | } else { 47 | val property: T? = configProvider.getOrNull(name, type, default) 48 | if (property != null) { 49 | cache[name] = property 50 | } 51 | property 52 | } 53 | } 54 | 55 | override fun bind(prefix: String, type: Class): T { 56 | // This is using %pre. in order to not collide with general properties 57 | val cachePrefix = "%pre.$prefix" 58 | return if (cache.containsKey(cachePrefix)) { 59 | cache[cachePrefix] as T 60 | } else { 61 | val property: T = configProvider.bind(prefix, type) 62 | cache[cachePrefix] = property 63 | property 64 | } 65 | } 66 | 67 | } -------------------------------------------------------------------------------- /cfg4k-core/src/main/kotlin/com/jdiazcano/cfg4k/providers/ConfigProvider.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 Javier Díaz-Cano Martín-Albo (javierdiazcanom@gmail.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jdiazcano.cfg4k.providers 18 | 19 | import com.jdiazcano.cfg4k.binders.Binder 20 | import com.jdiazcano.cfg4k.binders.DataClassBinder 21 | import com.jdiazcano.cfg4k.core.ConfigContext 22 | import com.jdiazcano.cfg4k.core.ConfigObject 23 | import com.jdiazcano.cfg4k.utils.typeOf 24 | import java.lang.reflect.Type 25 | 26 | /** 27 | * Base interface for all the ConfigProviders, this interface defines the needed methods of a provider in order to be 28 | * configurable and reloadable. 29 | */ 30 | interface ConfigProvider { 31 | 32 | val binder: Binder 33 | 34 | /** 35 | * Gets a property from the loader and parses it to the correct type. This is used for generics (but not only) so 36 | * this method will correctly parse List for example. 37 | * 38 | * @param name Name of the property 39 | * @param type Type of the property. (You can get the type with typeOf() method) 40 | */ 41 | fun get(name: String, type: Type, default: T? = null): T 42 | 43 | /** 44 | * Gets a property from the loader and parses it to the correct type. This is used for generics (but not only) so 45 | * this method will correctly parse List for example. 46 | * 47 | * @param name Name of the property 48 | * @param type Type of the property. (You can get the type with typeOf() method) 49 | */ 50 | fun getOrNull(name: String, type: Type, default: T? = null): T? 51 | 52 | fun load(name: String): ConfigObject? 53 | 54 | /** 55 | * This method will be called when there is an order to reload the properties. This can happen in different scenarios 56 | * for example with a timed reload strategy. This method will be called from the reload strategy. 57 | */ 58 | fun reload() 59 | 60 | /** 61 | * This method will be called in order to stop the automatic reloading. This will deregister itself from the 62 | * Reload strategy 63 | */ 64 | fun cancelReload(): Unit? 65 | 66 | /** 67 | * Adds a reload listener that will be called once the reload is performed. 68 | */ 69 | fun addReloadListener(listener: () -> Unit) 70 | 71 | /** 72 | * Adds a reload listener that will be called when there is an exception in the reload process. 73 | */ 74 | fun addReloadErrorListener(listener: (Exception) -> Unit) 75 | 76 | /** 77 | * Checks if a property exists in the provider. 78 | */ 79 | fun contains(name: String): Boolean 80 | 81 | /** 82 | * Binds an interface to a prefix 83 | * 84 | * This method will return an implementation of the interface with the given methods for configuration. (Will call 85 | * the Binder.bind method) 86 | * 87 | * @param prefix The prefix of the configuration, if this is not empty, configs starting with the prefix will be used 88 | * @param type The interface that will be implemented and it will be returned 89 | */ 90 | fun bind(prefix: String, type: Class): T { 91 | return when { 92 | type.kotlin.isData -> DataClassBinder.bind(this, prefix, type.kotlin) 93 | else -> binder.bind(this, prefix, type) 94 | } 95 | } 96 | } 97 | 98 | fun ConfigProvider.load(configContext: ConfigContext) = load(configContext.propertyName) 99 | inline fun ConfigProvider.bind(name: String = "") = bind(name, T::class.java) 100 | inline fun ConfigProvider.get(name: String = "", default: T? = null) = get(name, typeOf(), default) 101 | inline fun ConfigProvider.getOrNull(name: String = "", default: T? = null) = getOrNull(name, typeOf(), default) 102 | fun ConfigProvider.cache() = CachedConfigProvider(this) -------------------------------------------------------------------------------- /cfg4k-core/src/main/kotlin/com/jdiazcano/cfg4k/providers/DefaultConfigProvider.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 Javier Díaz-Cano Martín-Albo (javierdiazcanom@gmail.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jdiazcano.cfg4k.providers 18 | 19 | import com.jdiazcano.cfg4k.binders.Binder 20 | import com.jdiazcano.cfg4k.binders.ProxyBinder 21 | import com.jdiazcano.cfg4k.binders.convertGet 22 | import com.jdiazcano.cfg4k.binders.convertGetOrNull 23 | import com.jdiazcano.cfg4k.core.ConfigContext 24 | import com.jdiazcano.cfg4k.core.ConfigObject 25 | import com.jdiazcano.cfg4k.loaders.ConfigLoader 26 | import com.jdiazcano.cfg4k.parsers.Parsers.isExtendedParseable 27 | import com.jdiazcano.cfg4k.reloadstrategies.ReloadStrategy 28 | import com.jdiazcano.cfg4k.utils.ParserClassNotFound 29 | import com.jdiazcano.cfg4k.utils.SettingNotFound 30 | import com.jdiazcano.cfg4k.utils.convert 31 | import com.jdiazcano.cfg4k.utils.typeOf 32 | import java.lang.reflect.Type 33 | 34 | @Suppress("UNCHECKED_CAST") 35 | open class DefaultConfigProvider( 36 | private val configLoader: ConfigLoader, 37 | private val reloadStrategy: ReloadStrategy? = null, 38 | override val binder: Binder = ProxyBinder() 39 | ) : ConfigProvider { 40 | 41 | private val listeners: MutableList<() -> Unit> = mutableListOf() 42 | private val errorReloadListeners: MutableList<(Exception) -> Unit> = mutableListOf() 43 | private val changeListeners: HashMap Unit> = HashMap() 44 | 45 | init { 46 | reloadStrategy?.register(this) 47 | } 48 | 49 | override fun get(name: String, type: Type, default: T?): T { 50 | 51 | val value = configLoader.get(name) 52 | return if (value != null) { 53 | val structure = type.convert() 54 | val context = ConfigContext(this, name) 55 | 56 | convertGet(context, value, structure) as T 57 | } else { 58 | default ?: throw SettingNotFound(name) 59 | } 60 | } 61 | 62 | override fun load(name: String): ConfigObject? { 63 | return configLoader.get(name) 64 | } 65 | 66 | override fun getOrNull(name: String, type: Type, default: T?): T? { 67 | val value = configLoader.get(name) 68 | return if (value != null) { 69 | val structure = type.convert() 70 | val context = ConfigContext(this, name) 71 | 72 | when { 73 | structure.raw.isExtendedParseable() -> convertGetOrNull(context, value, structure) as T 74 | structure.raw.isInterface -> bind(name, structure.raw) as T 75 | else -> throw ParserClassNotFound("Parser for class $type was not found") 76 | } 77 | } else { 78 | default 79 | } 80 | } 81 | 82 | override fun contains(name: String) = configLoader.get(name) != null 83 | 84 | override fun cancelReload() = reloadStrategy?.deregister(this) 85 | 86 | override fun reload() { 87 | try { 88 | val keysBefore = changeListeners.keys.associateBy({ it }, { getOrNull(it.name, it.type) }) 89 | configLoader.reload() 90 | val keysAfter = changeListeners.keys.associateBy({ it }, { getOrNull(it.name, it.type) }) 91 | listeners.forEach { it() } // call listeners 92 | changeListeners.forEach { key, function -> 93 | function(keysBefore[key], keysAfter[key]) 94 | } 95 | } catch (e: Exception) { 96 | errorReloadListeners.forEach { it(e) } 97 | } 98 | } 99 | 100 | override fun addReloadListener(listener: () -> Unit) { 101 | listeners.add(listener) 102 | } 103 | 104 | override fun addReloadErrorListener(listener: (Exception) -> Unit) { 105 | errorReloadListeners.add(listener) 106 | } 107 | 108 | fun addChangeListener(name: String, type: Type, function: (T, T) -> Unit) { 109 | changeListeners[ListenerKey(name, type)] = function as (Any?, Any?) -> Unit 110 | } 111 | 112 | fun removeListener(name: String, type: Type) { 113 | changeListeners.remove(ListenerKey(name, type)) 114 | } 115 | 116 | } 117 | 118 | private data class ListenerKey(val name: String, val type: Type) 119 | 120 | inline fun DefaultConfigProvider.addChangeListener(name: String, noinline function: (T, T) -> Unit) = 121 | addChangeListener(name, typeOf(), function) 122 | -------------------------------------------------------------------------------- /cfg4k-core/src/main/kotlin/com/jdiazcano/cfg4k/providers/OverrideConfigProvider.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 Javier Díaz-Cano Martín-Albo (javierdiazcanom@gmail.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @file:Suppress("UNCHECKED_CAST") 18 | 19 | package com.jdiazcano.cfg4k.providers 20 | 21 | import com.jdiazcano.cfg4k.binders.ProxyBinder 22 | import com.jdiazcano.cfg4k.core.ConfigObject 23 | import com.jdiazcano.cfg4k.reloadstrategies.ReloadStrategy 24 | import com.jdiazcano.cfg4k.utils.SettingNotFound 25 | import java.lang.reflect.Type 26 | 27 | class OverrideConfigProvider( 28 | private vararg val providers: ConfigProvider, 29 | private val reloadStrategy: ReloadStrategy? = null 30 | ) : ConfigProvider { 31 | 32 | override val binder = ProxyBinder() 33 | private val listeners = mutableListOf<() -> Unit>() 34 | private val errorReloadListeners = mutableListOf<(Exception) -> Unit>() 35 | private val cachedProviders = mutableMapOf() 36 | 37 | init { 38 | reloadStrategy?.register(this) 39 | 40 | addReloadListener { cachedProviders.clear() } 41 | } 42 | 43 | override fun load(name: String): ConfigObject? { 44 | if (name in cachedProviders) { 45 | return cachedProviders[name]!!.load(name) 46 | } else { 47 | for (provider in providers) { 48 | if (provider.contains(name)) { 49 | cachedProviders[name] = provider 50 | return provider.load(name) 51 | } 52 | } 53 | } 54 | 55 | return null 56 | } 57 | 58 | override fun get(name: String, type: Type, default: T?): T { 59 | if (name in cachedProviders) { 60 | return cachedProviders[name]!!.get(name, type, default) 61 | } else { 62 | for (provider in providers) { 63 | if (provider.contains(name)) { 64 | cachedProviders[name] = provider 65 | return provider.get(name, type, default) 66 | } 67 | } 68 | } 69 | 70 | if (default != null) { 71 | return default 72 | } else { 73 | throw SettingNotFound(name) 74 | } 75 | } 76 | 77 | override fun getOrNull(name: String, type: Type, default: T?): T? { 78 | if (name in cachedProviders) { 79 | return cachedProviders[name]!!.getOrNull(name, type, default) 80 | } else { 81 | for (provider in providers) { 82 | if (provider.contains(name)) { 83 | cachedProviders[name] = provider 84 | return provider.getOrNull(name, type, default) 85 | } 86 | } 87 | } 88 | 89 | return default 90 | } 91 | 92 | override fun contains(name: String): Boolean { 93 | providers.forEach { 94 | if (it.contains(name)) { 95 | return true 96 | } 97 | } 98 | return false 99 | } 100 | 101 | override fun reload() { 102 | try { 103 | providers.forEach { it.reload() } 104 | listeners.forEach { it() } 105 | } catch (e: Exception) { 106 | errorReloadListeners.forEach { it(e) } 107 | } 108 | 109 | } 110 | 111 | override fun cancelReload(): Unit? { 112 | return reloadStrategy?.deregister(this) 113 | } 114 | 115 | override fun addReloadListener(listener: () -> Unit) { 116 | listeners.add(listener) 117 | } 118 | 119 | override fun addReloadErrorListener(listener: (Exception) -> Unit) { 120 | errorReloadListeners.add(listener) 121 | } 122 | 123 | } -------------------------------------------------------------------------------- /cfg4k-core/src/main/kotlin/com/jdiazcano/cfg4k/providers/Providers.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.providers 2 | 3 | import com.jdiazcano.cfg4k.loaders.ConfigLoader 4 | import com.jdiazcano.cfg4k.reloadstrategies.ReloadStrategy 5 | 6 | object Providers { 7 | fun cached(provider: ConfigProvider) = CachedConfigProvider(provider) 8 | 9 | fun overriden(vararg providers: ConfigProvider) = OverrideConfigProvider(*providers) 10 | 11 | fun proxy(configLoader: ConfigLoader, reloadStrategy: ReloadStrategy? = null) = ProxyConfigProvider(configLoader, reloadStrategy) 12 | } -------------------------------------------------------------------------------- /cfg4k-core/src/main/kotlin/com/jdiazcano/cfg4k/providers/ProxyConfigProvider.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 Javier Díaz-Cano Martín-Albo (javierdiazcanom@gmail.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jdiazcano.cfg4k.providers 18 | 19 | import com.jdiazcano.cfg4k.binders.ProxyBinder 20 | import com.jdiazcano.cfg4k.loaders.ConfigLoader 21 | import com.jdiazcano.cfg4k.reloadstrategies.ReloadStrategy 22 | 23 | @Suppress("UNCHECKED_CAST") 24 | class ProxyConfigProvider( 25 | configLoader: ConfigLoader, 26 | reloadStrategy: ReloadStrategy? = null 27 | ) : DefaultConfigProvider(configLoader, reloadStrategy, ProxyBinder()) -------------------------------------------------------------------------------- /cfg4k-core/src/main/kotlin/com/jdiazcano/cfg4k/reloadstrategies/FileChangeReloadStrategy.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 Javier Díaz-Cano Martín-Albo (javierdiazcanom@gmail.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jdiazcano.cfg4k.reloadstrategies 18 | 19 | import com.jdiazcano.cfg4k.providers.ConfigProvider 20 | import java.io.File 21 | import java.nio.file.FileSystems 22 | import java.nio.file.Files 23 | import java.nio.file.Path 24 | import java.nio.file.Paths 25 | import java.nio.file.StandardWatchEventKinds 26 | import kotlin.concurrent.thread 27 | 28 | /** 29 | * This reload strategy will reload the config provider when the timer ticks. A TimeUnit is used which will be 30 | * translated to milliseconds. 31 | */ 32 | class FileChangeReloadStrategy(val file: Path) : ReloadStrategy { 33 | 34 | private val watcher = FileSystems.getDefault().newWatchService() 35 | private var watching = false 36 | private lateinit var thread: Thread 37 | 38 | constructor(file: String) : this(Paths.get(file)) 39 | 40 | constructor(file: File) : this(file.toPath()) 41 | 42 | init { 43 | if (file.toFile().isDirectory) { 44 | throw IllegalArgumentException("$file must not be a directory") 45 | } 46 | } 47 | 48 | override fun register(configProvider: ConfigProvider) { 49 | // if its a symlink, then we should also watch symlinks for changes, this supports Kubernetes-style ConfigMap resources e.g. 50 | // configfile -> ..data/configfile 51 | // ..data -> ..2019_09_20_05_25_13.543205648 52 | // ..2019_09_20_05_25_13.543205648/configfile 53 | // Here, Kubernetes creates a new timestamped directory when the configmap changes, and just modifies the ..data symlink to point to it 54 | // FileWatcher raises symlink changes (e.g. overwrite an existing link target on Linux via `ln -sfn`) as ENTRY_CREATE 55 | // we don't use toRealPath() here, because we *want* the parent the file appears to be in, not the file's real parent 56 | val fileWatcher = file.toAbsolutePath().parent.register(watcher, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY) 57 | 58 | watching = true 59 | thread = thread(start = true, isDaemon = true) { 60 | while (watching) { 61 | try { 62 | val key = watcher.take() 63 | fileWatcher.pollEvents().forEach { event -> 64 | if(event.kind() == StandardWatchEventKinds.OVERFLOW) return@forEach 65 | 66 | val fileName = event.context() as Path 67 | // if any entry in the chain of symbolic links leading to the actual file, including the actual 68 | // file itself, has been created/modified, reload 69 | val linkChain = generateSequence(file) { 70 | if (Files.isSymbolicLink(it)) { 71 | it.parent.resolve(Files.readSymbolicLink(it).first()) 72 | } else null 73 | }.toList() 74 | 75 | if (linkChain.contains(file.parent.resolve(fileName))) { 76 | configProvider.reload() 77 | } 78 | } 79 | 80 | val valid = key.reset() 81 | if (!valid) { 82 | watching = false 83 | } 84 | } catch (exception: InterruptedException) { 85 | watching = false 86 | } 87 | } 88 | } 89 | } 90 | 91 | override fun deregister(configProvider: ConfigProvider) { 92 | thread.interrupt() 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /cfg4k-core/src/main/kotlin/com/jdiazcano/cfg4k/reloadstrategies/ReloadStrategy.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 Javier Díaz-Cano Martín-Albo (javierdiazcanom@gmail.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jdiazcano.cfg4k.reloadstrategies 18 | 19 | import com.jdiazcano.cfg4k.providers.ConfigProvider 20 | 21 | /** 22 | * A reload strategy defines when a reload of the config loader will be reloaded. 23 | */ 24 | interface ReloadStrategy { 25 | 26 | /** 27 | * Registers a config provider to be reloaded once the time comes. 28 | */ 29 | fun register(configProvider: ConfigProvider) 30 | 31 | /** 32 | * Deregisters the config provider so it will not be reloaded anymore. 33 | */ 34 | fun deregister(configProvider: ConfigProvider) 35 | } 36 | -------------------------------------------------------------------------------- /cfg4k-core/src/main/kotlin/com/jdiazcano/cfg4k/reloadstrategies/TimedReloadStrategy.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 Javier Díaz-Cano Martín-Albo (javierdiazcanom@gmail.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jdiazcano.cfg4k.reloadstrategies 18 | 19 | import com.jdiazcano.cfg4k.providers.ConfigProvider 20 | import com.jdiazcano.cfg4k.reloadstrategies.TimedReloadStrategy.Mode.FIXED_DELAY 21 | import com.jdiazcano.cfg4k.reloadstrategies.TimedReloadStrategy.Mode.FIXED_RATE 22 | import java.util.concurrent.Executors 23 | import java.util.concurrent.ScheduledExecutorService 24 | import java.util.concurrent.ScheduledFuture 25 | import java.util.concurrent.TimeUnit 26 | 27 | /** 28 | * This reload strategy will reload the config provider when the timer ticks. A TimeUnit is used which will be 29 | * translated to milliseconds. 30 | */ 31 | class TimedReloadStrategy(private val time: Long, 32 | private val unit: TimeUnit, 33 | private val mode: Mode = FIXED_RATE) : ReloadStrategy { 34 | enum class Mode { 35 | FIXED_RATE, FIXED_DELAY 36 | } 37 | 38 | private val executor: ScheduledExecutorService by lazy { 39 | Executors.newSingleThreadScheduledExecutor { runnable -> 40 | Thread(runnable, "TimeReloadStrategy").also { it.isDaemon = true } 41 | } 42 | } 43 | private val reloadTasks = mutableMapOf>() 44 | 45 | override fun register(configProvider: ConfigProvider) { 46 | reloadTasks.computeIfAbsent(configProvider) { cp -> 47 | val reload = { cp.reload() } 48 | when (mode) { 49 | FIXED_RATE -> executor.scheduleAtFixedRate(reload, 0, time, unit) 50 | FIXED_DELAY -> executor.scheduleWithFixedDelay(reload, 0, time, unit) 51 | } 52 | } 53 | } 54 | 55 | override fun deregister(configProvider: ConfigProvider) { 56 | reloadTasks[configProvider]?.cancel(true) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /cfg4k-core/src/main/kotlin/com/jdiazcano/cfg4k/sources/ClasspathConfigSource.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.sources 2 | 3 | import java.io.InputStream 4 | 5 | class ClasspathConfigSource(private val resource: String): ConfigSource { 6 | override fun read(): InputStream { 7 | return ClasspathConfigSource::class.java.getResourceAsStream(resource) 8 | } 9 | } -------------------------------------------------------------------------------- /cfg4k-core/src/main/kotlin/com/jdiazcano/cfg4k/sources/CommonConfigSources.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.sources 2 | 3 | import java.io.ByteArrayInputStream 4 | import java.io.InputStream 5 | 6 | /** 7 | * Static String source. 8 | */ 9 | class StringConfigSource(private val string: String): ConfigSource { 10 | override fun read(): InputStream { 11 | return ByteArrayInputStream(string.toByteArray()) 12 | } 13 | } 14 | 15 | /** 16 | * Dynamic String source that will rotate through all the listed strings 17 | */ 18 | class StringRotationConfigSource(private val strings: List): ConfigSource { 19 | private var i = 0 20 | 21 | override fun read(): InputStream { 22 | return ByteArrayInputStream(strings[i++%strings.size].toByteArray()) 23 | } 24 | } 25 | 26 | /** 27 | * Config source that uses a function as input and returning a (primitive) ByteArray so you can define the data that you 28 | * want based on arbitrary logic. 29 | */ 30 | class FunctionConfigSource(private val function: () -> ByteArray): ConfigSource { 31 | override fun read(): InputStream { 32 | return ByteArrayInputStream(function()) 33 | } 34 | } 35 | 36 | /** 37 | * Config source that uses a function as input and returning a string so you can define the data that you want based on 38 | * arbitrary logic. 39 | */ 40 | class StringFunctionConfigSource(private val function: () -> String): ConfigSource { 41 | override fun read(): InputStream { 42 | return ByteArrayInputStream(function().toByteArray()) 43 | } 44 | } -------------------------------------------------------------------------------- /cfg4k-core/src/main/kotlin/com/jdiazcano/cfg4k/sources/ConfigSource.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.sources 2 | 3 | import java.io.InputStream 4 | 5 | interface ConfigSource { 6 | fun read(): InputStream 7 | } 8 | 9 | /** 10 | * A class to just proxy the InputStream to a ConfigSource 11 | */ 12 | class InputConfigSource(private val inputStream: InputStream): ConfigSource { 13 | override fun read() = inputStream 14 | } 15 | 16 | /** 17 | * Convert this InputStream to a ConfigSource 18 | */ 19 | fun InputStream.toConfigSource() = InputConfigSource(this) -------------------------------------------------------------------------------- /cfg4k-core/src/main/kotlin/com/jdiazcano/cfg4k/sources/FileConfigSource.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.sources 2 | 3 | import java.io.File 4 | import java.io.InputStream 5 | import java.nio.file.Path 6 | 7 | class FileConfigSource(private val file: File): ConfigSource { 8 | override fun read(): InputStream { 9 | return file.inputStream() 10 | } 11 | } 12 | 13 | fun FileConfigSource(path: Path) = FileConfigSource(path.toFile()) 14 | -------------------------------------------------------------------------------- /cfg4k-core/src/main/kotlin/com/jdiazcano/cfg4k/sources/URLConfigSource.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.sources 2 | 3 | import java.io.InputStream 4 | import java.net.HttpURLConnection 5 | import java.net.URL 6 | import java.util.Base64 7 | 8 | class URLConfigSource(val url: URL, 9 | private val authHeader: String? = null) : ConfigSource { 10 | override fun read(): InputStream { 11 | val connection = url.openConnection() 12 | if (connection is HttpURLConnection && authHeader != null) { 13 | connection.setRequestProperty("Authorization", authHeader) 14 | } 15 | 16 | return connection.inputStream 17 | } 18 | } 19 | 20 | 21 | private const val GITHUB_RAW_BASE_URL = "https://raw.githubusercontent.com" 22 | fun GitHubConfigSource(repoOwner: String, 23 | repoSlug: String, 24 | configFilePath: String, 25 | repoBranch: String = "master"): ConfigSource { 26 | return URLConfigSource(URL("$GITHUB_RAW_BASE_URL/$repoOwner/$repoSlug/$repoBranch/$configFilePath")) 27 | } 28 | 29 | 30 | private const val BITBUCKET_API_BASE_URL = "https://api.bitbucket.org" 31 | fun BitbucketConfigSource(repoOwner: String, 32 | repoSlug: String, 33 | configFilePath: String, 34 | repoBranch: String = "master", 35 | authHeader: String? = null): ConfigSource { 36 | return URLConfigSource( 37 | URL("$BITBUCKET_API_BASE_URL/2.0/repositories/$repoOwner/$repoSlug/src/$repoBranch/$configFilePath"), 38 | authHeader 39 | ) 40 | } 41 | 42 | fun BitbucketUserConfigSource(username: String, 43 | password: String, 44 | repoOwner: String, 45 | repoSlug: String, 46 | configFilePath: String, 47 | repoBranch: String = "master"): ConfigSource { 48 | return BitbucketConfigSource(repoOwner, repoSlug, configFilePath, repoBranch, basicAuth(username, password)) 49 | } 50 | 51 | /** 52 | * Generates `Authorization` header value for basic authentication with the given [username] and [password]. 53 | */ 54 | fun basicAuth(username: String, password: String): String { 55 | val credentials = "$username:$password" 56 | val encodedCredentials = Base64.getEncoder().encodeToString(credentials.toByteArray()) 57 | return "Basic $encodedCredentials" 58 | } 59 | 60 | /** 61 | * Generates `Authorization` header value for a token (Used in github for example). 62 | */ 63 | fun String.asToken() = "Token $this" 64 | -------------------------------------------------------------------------------- /cfg4k-core/src/main/kotlin/com/jdiazcano/cfg4k/utils/Exceptions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 Javier Díaz-Cano Martín-Albo (javierdiazcanom@gmail.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jdiazcano.cfg4k.utils 18 | 19 | /** 20 | * This exception will be thrown when a setting is not found and there is no default value for it. 21 | */ 22 | class SettingNotFound(settingName: String) : RuntimeException("Setting '$settingName' not found.") 23 | 24 | /** 25 | * Thrown when the settings have not been initialised/loaded into memory and you try to access them. 26 | */ 27 | class SettingsNotInitialisedException(message: String) : Exception(message) 28 | 29 | /** 30 | * This exception will be thrown when a parser is not defined for a specific class. If you see this error you need to 31 | * call the methods "addParser" of the Provider 32 | */ 33 | class ParserClassNotFound(message: String) : Exception(message) 34 | 35 | /** 36 | * Exception thrown when trying to bind a data class and there is no matching constructor with the parameters 37 | */ 38 | class ConstructorNotFound(message: String) : Exception(message) -------------------------------------------------------------------------------- /cfg4k-core/src/main/kotlin/com/jdiazcano/cfg4k/utils/GenericType.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 Javier Díaz-Cano Martín-Albo (javierdiazcanom@gmail.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jdiazcano.cfg4k.utils 18 | 19 | import java.lang.reflect.ParameterizedType 20 | import java.lang.reflect.Type 21 | 22 | abstract class GenericType : Typable { 23 | private val type: Type 24 | 25 | init { 26 | val parameterizedType = javaClass.genericSuperclass 27 | 28 | if (parameterizedType is ParameterizedType) { 29 | type = parameterizedType.actualTypeArguments[0] 30 | } else { 31 | throw IllegalArgumentException("Class must be parameterized") 32 | } 33 | } 34 | 35 | override fun getType() = type 36 | 37 | override fun toString(): String { 38 | return "GenericType { type: $type }" 39 | } 40 | 41 | } 42 | 43 | interface Typable { 44 | fun getType(): Type 45 | } 46 | 47 | inline fun typeOf() = object : GenericType() {}.getType() -------------------------------------------------------------------------------- /cfg4k-core/src/main/kotlin/com/jdiazcano/cfg4k/utils/TargetType.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 Javier Díaz-Cano Martín-Albo (javierdiazcanom@gmail.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jdiazcano.cfg4k.utils 18 | 19 | import java.lang.reflect.ParameterizedType 20 | import java.lang.reflect.Type 21 | import java.lang.reflect.WildcardType 22 | 23 | fun Type.convert(): TypeStructure { 24 | val structure = TypeStructure(this) 25 | 26 | val dewildcarded = dewildcard(this) as? ParameterizedType ?: return structure 27 | 28 | structure.generics.addAll(dewildcarded.actualTypeArguments.map { convertType(it) }) 29 | 30 | return structure 31 | } 32 | 33 | private fun convertType(typeProvided: Type): TypeStructure { 34 | val type = dewildcard(typeProvided) 35 | return when (type) { 36 | is Class<*> -> TypeStructure(type) 37 | 38 | is ParameterizedType -> { 39 | TypeStructure(type, type.actualTypeArguments.map { convertType(it) }.toMutableList()) 40 | } 41 | 42 | else -> { 43 | throw UnsupportedOperationException( 44 | "That type contains illegal type argument: '$type' [${type.javaClass}]") 45 | } 46 | } 47 | } 48 | 49 | private fun dewildcard(type: Type) = 50 | if (type is WildcardType) type.upperBounds[0] else type 51 | 52 | data class TypeStructure( 53 | val type: Type, 54 | val generics: MutableList = arrayListOf() 55 | ) { 56 | val raw = if (type is ParameterizedType) type.rawType as Class<*> else type as Class<*> 57 | 58 | fun isMap() = type is ParameterizedType && type.rawType is Class<*> && (type.rawType as Class<*>).isAssignableFrom(Map::class.java) 59 | } -------------------------------------------------------------------------------- /cfg4k-core/src/test/kotlin/com/jdiazcano/cfg4k/SharedClasses.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k 2 | 3 | data class Potato(val name: String, val size: Int) 4 | 5 | data class ObjectWithAllTheThings( 6 | val intProperty: Int, 7 | val stringProperty: String, 8 | val longProperty: Long, 9 | val shortProperty: Short, 10 | val byteProperty: Byte, 11 | val doubleProperty: Double, 12 | val floatProperty: Float, 13 | val potato: Potato, 14 | val potatoList: List, 15 | val potatoMap: Map, 16 | val nullableString: String?, 17 | // val stringWithDefault: String = "def", TODO defaults in data classes aren't totally supported 18 | val randomThing: String? 19 | ) 20 | 21 | interface InterfacePropertyWithAllTheThings { 22 | val intProperty: Int 23 | val stringProperty: String 24 | val longProperty: Long 25 | val shortProperty: Short 26 | val byteProperty: Byte 27 | val doubleProperty: Double 28 | val floatProperty: Float 29 | val potato: Potato 30 | val potatoList: List 31 | val potatoMap: Map 32 | val randomThing: String? 33 | val nullableString: String? 34 | val stringWithDefault: String get() = "def" 35 | } 36 | 37 | interface InterfaceMethodWithAllTheThings { 38 | fun intProperty(): Int 39 | fun stringProperty(): String 40 | fun longProperty(): Long 41 | fun shortProperty(): Short 42 | fun byteProperty(): Byte 43 | fun doubleProperty(): Double 44 | fun floatProperty(): Float 45 | fun potato(): Potato 46 | fun potatoList(): List 47 | fun potatoMap(): Map 48 | fun randomThing(): String? 49 | fun nullableString(): String? 50 | fun stringWithDefault(): String = "def" 51 | } -------------------------------------------------------------------------------- /cfg4k-core/src/test/kotlin/com/jdiazcano/cfg4k/binders/BindingInvocationHandlerKtTest.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.binders 2 | 3 | import io.kotlintest.specs.StringSpec 4 | import io.kotlintest.shouldBe 5 | 6 | class BindingInvocationHandlerKtTest : StringSpec({ 7 | 8 | "should strip the get is or has from the methods that needs" { 9 | getPropertyName("getTest") shouldBe "test" 10 | getPropertyName("isomorphic") shouldBe "isomorphic" 11 | getPropertyName("isOmorphic") shouldBe "omorphic" 12 | getPropertyName("hash") shouldBe "hash" 13 | getPropertyName("hasTests") shouldBe "tests" 14 | getPropertyName("getИмя") shouldBe "имя" 15 | getPropertyName("getимя") shouldBe "getимя" 16 | } 17 | 18 | }) -------------------------------------------------------------------------------- /cfg4k-core/src/test/kotlin/com/jdiazcano/cfg4k/core/ConfigObjectTest.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.core 2 | 3 | import io.kotlintest.specs.StringSpec 4 | import io.kotlintest.shouldBe 5 | import io.kotlintest.shouldThrow 6 | import java.io.InvalidObjectException 7 | 8 | class ConfigObjectTest : StringSpec({ 9 | 10 | "string config object merge" { 11 | val first = "a".toConfig() 12 | val second = "a".toConfig() 13 | 14 | first.isString() shouldBe true 15 | shouldThrow { 16 | first.merge(second) 17 | } 18 | } 19 | 20 | "map config object merge" { 21 | val first = mapOf("a" to "b").toConfig() 22 | val second = mapOf("b" to "c").toConfig() 23 | 24 | first.isObject() shouldBe true 25 | first.merge(second) shouldBe mapOf("a" to "b", "b" to "c").toConfig() 26 | } 27 | 28 | "map config object merge throws if two string values" { 29 | val first = mapOf("a" to "b").toConfig() 30 | val second = mapOf("a" to "c").toConfig() 31 | 32 | shouldThrow { 33 | first.merge(second) shouldBe mapOf("a" to "b", "a" to "c").toConfig() 34 | } 35 | } 36 | 37 | "map config object merge with lists" { 38 | val first = mapOf("a" to listOf("b", "c")).toConfig() 39 | val second = mapOf("a" to listOf("c", "d")).toConfig() 40 | 41 | first.merge(second) shouldBe mapOf("a" to listOf("b", "c", "d")).toConfig() 42 | } 43 | 44 | "map config object merge with lists and first objects aren't modified" { 45 | val first = mapOf("a" to listOf("b", "c")).toConfig() 46 | val second = mapOf("a" to listOf("c", "d")).toConfig() 47 | val merge = first.merge(second) 48 | 49 | first shouldBe mapOf("a" to listOf("b", "c")).toConfig() 50 | second shouldBe mapOf("a" to listOf("c", "d")).toConfig() 51 | } 52 | 53 | "list merge" { 54 | val first = listOf("a").toConfig() 55 | val second = listOf("b").toConfig() 56 | 57 | first.merge(second) shouldBe listOf("a", "b").toConfig() 58 | } 59 | 60 | "nested merge" { 61 | val list1 = MapConfigObject(mapOf( 62 | "a" to ListConfigObject(listOf(1.toConfig(), 2.toConfig())), 63 | "b" to MapConfigObject(mapOf( 64 | "aa" to 11.toConfig(), 65 | "ab" to 22.toConfig(), 66 | "ac" to 33.toConfig() 67 | )) 68 | )) 69 | 70 | val list2 = MapConfigObject(mapOf( 71 | "a" to ListConfigObject(listOf(3.toConfig())), 72 | "c" to 3.toConfig(), 73 | "d" to 4.toConfig() 74 | )) 75 | 76 | val mergedList = list1.merge(list2) 77 | mergedList shouldBe MapConfigObject(mapOf( 78 | "a" to ListConfigObject(listOf(1.toConfig(), 2.toConfig(), 3.toConfig())), 79 | "b" to MapConfigObject(mapOf( 80 | "aa" to 11.toConfig(), 81 | "ab" to 22.toConfig(), 82 | "ac" to 33.toConfig() 83 | )), 84 | "c" to 3.toConfig(), 85 | "d" to 4.toConfig() 86 | )) 87 | } 88 | 89 | "config to string" { 90 | 1.toConfig().toString() shouldBe "ConfigObject(value=1)" 91 | "a".toConfig().toString() shouldBe "ConfigObject(value=a)" 92 | true.toConfig().toString() shouldBe "ConfigObject(value=true)" 93 | 1L.toConfig().toString() shouldBe "ConfigObject(value=1)" 94 | 1.1.toConfig().toString() shouldBe "ConfigObject(value=1.1)" 95 | 1.1F.toConfig().toString() shouldBe "ConfigObject(value=1.1)" 96 | } 97 | 98 | "extension converters" { 99 | 1.toConfig() shouldBe StringConfigObject("1") 100 | "a".toConfig() shouldBe StringConfigObject("a") 101 | true.toConfig() shouldBe StringConfigObject("true") 102 | 1L.toConfig() shouldBe StringConfigObject("1") 103 | 1.1.toConfig() shouldBe StringConfigObject("1.1") 104 | 1.1F.toConfig() shouldBe StringConfigObject("1.1") 105 | } 106 | 107 | }) -------------------------------------------------------------------------------- /cfg4k-core/src/test/kotlin/com/jdiazcano/cfg4k/loaders/EnvironmentConfigLoaderTest.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.loaders 2 | 3 | import com.jdiazcano.cfg4k.core.toConfig 4 | import io.kotlintest.Spec 5 | import io.kotlintest.TestCase 6 | import io.kotlintest.TestResult 7 | import io.kotlintest.extensions.TopLevelTest 8 | import io.kotlintest.matchers.types.shouldBeNull 9 | import io.kotlintest.shouldBe 10 | import io.kotlintest.specs.StringSpec 11 | import io.mockk.every 12 | import io.mockk.mockkStatic 13 | import io.mockk.unmockkAll 14 | 15 | class EnvironmentConfigLoaderTest: StringSpec() { 16 | 17 | override fun beforeSpecClass(spec: Spec, tests: List) { 18 | mockkStatic(System::class) 19 | every { System.getenv() } returns mapOf( 20 | "PROPERTIES_GROUPONE_KEYONE" to "1", 21 | "PROPERTIES_GROUPONE_KEYTWO" to "2" 22 | ) 23 | } 24 | 25 | override fun afterSpecClass(spec: Spec, results: Map) { 26 | unmockkAll() 27 | } 28 | 29 | init { 30 | val loader by lazy { EnvironmentConfigLoader() } 31 | 32 | "it should be good in the loader" { 33 | loader.get("properties.groupone.keyone") shouldBe "1".toConfig() 34 | } 35 | 36 | "null if not found" { 37 | loader.get("this.is.not.found").shouldBeNull() 38 | } 39 | 40 | "updated when reloading" { 41 | loader.get("properties.groupone.keyone") shouldBe "1".toConfig() 42 | every { System.getenv() } returns mapOf( 43 | "PROPERTIES_GROUPONE_KEYONE" to "11", 44 | "PROPERTIES_GROUPONE_KEYTWO" to "22" 45 | ) 46 | loader.reload() 47 | loader.get("properties.groupone.keyone") shouldBe "11".toConfig() 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /cfg4k-core/src/test/kotlin/com/jdiazcano/cfg4k/loaders/PropertyConfigLoaderTest.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.loaders 2 | 3 | import com.jdiazcano.cfg4k.core.ListConfigObject 4 | import com.jdiazcano.cfg4k.core.MapConfigObject 5 | import com.jdiazcano.cfg4k.core.toConfig 6 | import com.jdiazcano.cfg4k.sources.StringRotationConfigSource 7 | import io.kotlintest.* 8 | import io.kotlintest.matchers.types.shouldBeNull 9 | import io.kotlintest.specs.StringSpec 10 | 11 | class PropertyConfigLoaderTest : StringSpec({ 12 | 13 | val source = StringRotationConfigSource(listOf( 14 | "a=1", 15 | "a=2", 16 | "a\\u1aa", 17 | "a=1\na.b=2" 18 | )) 19 | val loader by lazy { PropertyConfigLoader(source) } 20 | 21 | "it should be good in the loader" { 22 | loader.get("a").shouldBe("1".toConfig()) 23 | } 24 | 25 | "null if not found" { 26 | loader.get("this.is.not.found").shouldBeNull() 27 | } 28 | 29 | "updated when reloading" { 30 | loader.get("a") shouldBe "1".toConfig() 31 | loader.reload() 32 | loader.get("a") shouldBe "2".toConfig() 33 | } 34 | 35 | "error when reloading a bad file" { 36 | loader.get("a") shouldBe "2".toConfig() 37 | shouldThrow { 38 | loader.reload() 39 | } 40 | } 41 | 42 | "simple map is converted to properties and config" { 43 | val config = mapOf( 44 | "a" to "1", 45 | "b" to "2" 46 | ).toProperties().toConfig() 47 | 48 | config shouldBe MapConfigObject(mapOf( 49 | "a" to 1.toConfig(), 50 | "b" to 2.toConfig() 51 | )) 52 | } 53 | 54 | "lists are parsed properly in properties" { 55 | val config = mapOf( 56 | "a[0]" to "1", 57 | "a[1]" to "2" 58 | ).toProperties().toConfig() 59 | 60 | config shouldBe MapConfigObject(mapOf("a" to ListConfigObject(listOf(1.toConfig(), 2.toConfig())))) 61 | } 62 | 63 | "lists are parsed properly in properties with properties in the middle" { 64 | val config = mapOf( 65 | "a[0]" to "1", 66 | "randomProperty" to "random", 67 | "a[1]" to "2" 68 | ).toProperties().toConfig() 69 | 70 | config shouldBe MapConfigObject(mapOf( 71 | "a" to ListConfigObject(listOf(1.toConfig(), 2.toConfig())), 72 | "randomProperty" to "random".toConfig() 73 | )) 74 | } 75 | 76 | // There's still no ability of setting the index when defining a property 77 | "!lists are parsed properly in properties with properties in the middle reverse order declaration" { 78 | val config = mapOf( 79 | "a[1]" to "1", 80 | "randomProperty" to "random", 81 | "a[0]" to "2" 82 | ).toProperties().toConfig() 83 | 84 | config shouldBe MapConfigObject(mapOf( 85 | "a" to ListConfigObject(listOf(1.toConfig(), 2.toConfig())), 86 | "randomProperty" to "random".toConfig() 87 | )) 88 | } 89 | 90 | "lists inside objects are parsed properly in properties" { 91 | val config = mapOf( 92 | "me.friends[0]" to "A", 93 | "me.friends[1]" to "B" 94 | ).toProperties().toConfig() 95 | 96 | config shouldBe MapConfigObject(mapOf( 97 | "me" to MapConfigObject(mapOf( 98 | "friends" to ListConfigObject(listOf("A".toConfig(), "B".toConfig())) 99 | )) 100 | )) 101 | } 102 | 103 | "lists inside objects with properties are parsed properly in properties" { 104 | val config = mapOf( 105 | "me.friends[0].name" to "A", 106 | "me.friends[1].name" to "B" 107 | ).toProperties().toConfig() 108 | 109 | config shouldBe MapConfigObject(mapOf( 110 | "me" to MapConfigObject(mapOf( 111 | "friends" to ListConfigObject(listOf( 112 | MapConfigObject(mapOf("name" to "A".toConfig())), 113 | MapConfigObject(mapOf("name" to "B".toConfig())) 114 | )) 115 | )) 116 | )) 117 | } 118 | 119 | "map is converted to properties and config" { 120 | val config = mapOf( 121 | "properties.groupone" to "1", 122 | "properties.groupone.keyone" to "1", 123 | "properties.groupone.keytwo" to "2" 124 | ).toProperties().toConfig() 125 | 126 | config shouldBe MapConfigObject(mapOf( 127 | "properties" to MapConfigObject(mapOf( 128 | "groupone" to MapConfigObject(mapOf( 129 | "keyone" to 1.toConfig(), 130 | "keytwo" to 2.toConfig() 131 | )) 132 | )) 133 | )) 134 | } 135 | }) -------------------------------------------------------------------------------- /cfg4k-core/src/test/kotlin/com/jdiazcano/cfg4k/loaders/SystemPropertyConfigLoaderTest.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.loaders 2 | 3 | import com.jdiazcano.cfg4k.core.toConfig 4 | import io.kotlintest.Spec 5 | import io.kotlintest.TestCase 6 | import io.kotlintest.TestResult 7 | import io.kotlintest.extensions.TopLevelTest 8 | import io.kotlintest.matchers.types.shouldBeNull 9 | import io.kotlintest.specs.StringSpec 10 | import io.kotlintest.shouldBe 11 | import io.mockk.every 12 | import io.mockk.mockkStatic 13 | import io.mockk.unmockkAll 14 | 15 | class SystemPropertyConfigLoaderTest : StringSpec() { 16 | 17 | override fun beforeSpecClass(spec: Spec, tests: List) { 18 | mockkStatic(System::class) 19 | every { System.getProperties() } returns mapOf( 20 | "properties.groupone.keyone" to "1", 21 | "properties.groupone.keytwo" to "2" 22 | ).toProperties() 23 | } 24 | 25 | override fun afterSpecClass(spec: Spec, results: Map) { 26 | unmockkAll() 27 | } 28 | 29 | init { 30 | val loader by lazy { SystemPropertyConfigLoader() } 31 | 32 | "it should be good in the loader" { 33 | loader.get("properties.groupone.keyone").shouldBe("1".toConfig()) 34 | } 35 | 36 | "null if not found" { 37 | loader.get("this.is.not.found").shouldBeNull() 38 | } 39 | 40 | "updated when reloading" { 41 | loader.get("properties.groupone.keyone") shouldBe "1".toConfig() 42 | every { System.getProperties() } returns mapOf( 43 | "properties.groupone.keyone" to "11", 44 | "properties.groupone.keytwo" to "22" 45 | ).toProperties() 46 | loader.reload() 47 | loader.get("properties.groupone.keyone") shouldBe "11".toConfig() 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /cfg4k-core/src/test/kotlin/com/jdiazcano/cfg4k/parsers/BigParsersTest.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.parsers 2 | 3 | import com.jdiazcano.cfg4k.core.ConfigContext 4 | import com.jdiazcano.cfg4k.core.toConfig 5 | import com.jdiazcano.cfg4k.loaders.EnvironmentConfigLoader 6 | import com.jdiazcano.cfg4k.providers.DefaultConfigProvider 7 | import io.kotlintest.shouldBe 8 | import io.kotlintest.specs.StringSpec 9 | import java.math.BigDecimal 10 | import java.math.BigInteger 11 | 12 | class BigParsersTest: StringSpec({ 13 | val emptyContext = ConfigContext(DefaultConfigProvider(EnvironmentConfigLoader()), "") 14 | 15 | "parsing a big integer" { 16 | BigIntegerParser.parse(emptyContext, "111111".toConfig()).shouldBe(BigInteger("111111")) 17 | } 18 | 19 | "parsing a big decimal" { 20 | BigDecimalParser.parse(emptyContext, "111111".toConfig()).shouldBe(BigDecimal("111111")) 21 | } 22 | }) -------------------------------------------------------------------------------- /cfg4k-core/src/test/kotlin/com/jdiazcano/cfg4k/parsers/CommonParsersTest.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.parsers 2 | 3 | import com.jdiazcano.cfg4k.core.ConfigContext 4 | import com.jdiazcano.cfg4k.core.toConfig 5 | import com.jdiazcano.cfg4k.loaders.EnvironmentConfigLoader 6 | import com.jdiazcano.cfg4k.providers.DefaultConfigProvider 7 | import io.kotlintest.shouldBe 8 | import io.kotlintest.specs.StringSpec 9 | import io.mockk.every 10 | import io.mockk.mockk 11 | import io.mockk.mockkStatic 12 | import io.mockk.unmockkStatic 13 | import java.net.InetAddress 14 | import java.sql.Connection 15 | import java.sql.Driver 16 | import java.sql.DriverManager 17 | import java.util.* 18 | 19 | class CommonParsersTest: StringSpec({ 20 | val emptyContext = ConfigContext(DefaultConfigProvider(EnvironmentConfigLoader()), "") 21 | 22 | "pattern parser" { 23 | PatternParser.parse(emptyContext, "a.*".toConfig()).pattern().shouldBe("a.*") 24 | } 25 | 26 | "RegexParser" { 27 | RegexParser.parse(emptyContext, "a.*".toConfig()).pattern.shouldBe("a.*") 28 | } 29 | 30 | "UUIDParser" { 31 | UUIDParser.parse(emptyContext, 32 | "c796118e-8a67-4eb7-adcf-62132a2f095d".toConfig()) 33 | .shouldBe(UUID.fromString("c796118e-8a67-4eb7-adcf-62132a2f095d")) 34 | } 35 | 36 | "SQLDriverParser" { 37 | mockkStatic(DriverManager::class) 38 | val driver = mockk() 39 | every { DriverManager.getDriver(any()) } returns driver 40 | 41 | SQLDriverParser.parse(emptyContext, "random".toConfig()).shouldBe(driver) 42 | 43 | unmockkStatic(DriverManager::class) 44 | } 45 | 46 | "SQLConnectionParser" { 47 | mockkStatic(DriverManager::class) 48 | val driver = mockk() 49 | every { DriverManager.getConnection(any()) } returns driver 50 | 51 | SQLConnectionParser.parse(emptyContext, "random".toConfig()).shouldBe(driver) 52 | 53 | unmockkStatic(DriverManager::class) 54 | } 55 | 56 | "InetAddressParser" { 57 | InetAddressParser.parse(emptyContext, "127.0.0.1".toConfig()).shouldBe(InetAddress.getByName("127.0.0.1")) 58 | } 59 | }) -------------------------------------------------------------------------------- /cfg4k-core/src/test/kotlin/com/jdiazcano/cfg4k/parsers/EnumParserTest.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.parsers 2 | 3 | import com.jdiazcano.cfg4k.core.ConfigContext 4 | import com.jdiazcano.cfg4k.core.toConfig 5 | import com.jdiazcano.cfg4k.utils.TypeStructure 6 | import io.kotlintest.shouldBe 7 | import io.kotlintest.specs.StringSpec 8 | import io.mockk.mockk 9 | 10 | class EnumParserTest: StringSpec({ 11 | val context = mockk() 12 | 13 | "Parses an enum correctly" { 14 | EnumParser().parse(context, "TYPE".toConfig(), TypeStructure(EnumType::class.java)) 15 | .shouldBe(EnumType.TYPE) 16 | } 17 | 18 | }) 19 | 20 | enum class EnumType { 21 | TYPE 22 | ; 23 | } -------------------------------------------------------------------------------- /cfg4k-core/src/test/kotlin/com/jdiazcano/cfg4k/parsers/FileParsersTest.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.parsers 2 | 3 | import com.jdiazcano.cfg4k.core.ConfigContext 4 | import com.jdiazcano.cfg4k.core.toConfig 5 | import io.kotlintest.shouldBe 6 | import io.kotlintest.specs.StringSpec 7 | import io.mockk.mockk 8 | import java.io.File 9 | import java.net.URI 10 | import java.net.URL 11 | import java.nio.file.Paths 12 | 13 | class FileParsersTest: StringSpec({ 14 | val context = mockk() 15 | 16 | "FileParser parses properly" { 17 | FileParser.parse(context, "myfile.txt".toConfig()).shouldBe(File("myfile.txt")) 18 | } 19 | 20 | "PathParser parses properly" { 21 | PathParser.parse(context, "mypath/test.txt".toConfig()).shouldBe(Paths.get("mypath/test.txt")) 22 | } 23 | 24 | "URIParser parses properly" { 25 | URIParser.parse(context, "https://www.amazon.com".toConfig()).shouldBe(URI("https://www.amazon.com")) 26 | } 27 | 28 | "URLParser parses properly" { 29 | URLParser.parse(context, "https://www.amazon.com".toConfig()).shouldBe(URL("https://www.amazon.com")) 30 | } 31 | 32 | }) -------------------------------------------------------------------------------- /cfg4k-core/src/test/kotlin/com/jdiazcano/cfg4k/parsers/ListParserTest.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.parsers 2 | 3 | import com.jdiazcano.cfg4k.Potato 4 | import com.jdiazcano.cfg4k.core.ConfigContext 5 | import com.jdiazcano.cfg4k.json.JsonConfigLoader 6 | import com.jdiazcano.cfg4k.providers.DefaultConfigProvider 7 | import com.jdiazcano.cfg4k.sources.StringConfigSource 8 | import com.jdiazcano.cfg4k.utils.TypeStructure 9 | import com.jdiazcano.cfg4k.utils.typeOf 10 | import io.kotlintest.shouldBe 11 | import io.kotlintest.specs.StringSpec 12 | 13 | class ListParserTest: StringSpec({ 14 | val json = """ 15 | { 16 | "liststr": [ "a", "b" ], 17 | "listliststr": [ [ "a" ], [ "b" ] ], 18 | "listint": [ 1, 2 ], 19 | "listlistint": [ [ 1 ], [ 2 ] ], 20 | "potatolist": [ 21 | { 22 | "name": "Super potato", 23 | "size": 1 24 | } 25 | ] 26 | } 27 | """.trimIndent() 28 | val loader = JsonConfigLoader(StringConfigSource(json)) 29 | val provider = DefaultConfigProvider(loader) 30 | val context = ConfigContext(provider, "") 31 | 32 | "can parse a normal list of strings" { 33 | ListParser.parse(context, loader.get("liststr")!!, listStructure()) 34 | .shouldBe(listOf("a", "b")) 35 | } 36 | 37 | "can parse a normal list of integers" { 38 | ListParser.parse(context, loader.get("listint")!!, listStructure()) 39 | .shouldBe(listOf(1, 2)) 40 | } 41 | 42 | "can parse a normal String to List map" { 43 | ListParser.parse(context, loader.get("listliststr")!!, listListStructure()) 44 | .shouldBe(listOf(listOf("a"), listOf("b"))) 45 | } 46 | 47 | "can parse a normal String to List map" { 48 | ListParser.parse(context, loader.get("listlistint")!!, listListStructure()) 49 | .shouldBe(listOf(listOf(1), listOf(2))) 50 | } 51 | 52 | "can parse a normal String to List map" { 53 | ListParser.parse(context.copy(propertyName = "potatolist"), loader.get("potatolist")!!, listStructure()) 54 | .shouldBe(listOf(Potato("Super potato", 1))) 55 | } 56 | }) 57 | 58 | inline fun listStructure(): TypeStructure { 59 | return TypeStructure( 60 | typeOf>(), 61 | arrayListOf( 62 | TypeStructure(typeOf(), arrayListOf(TypeStructure(TYPE::class.java))) 63 | ) 64 | ) 65 | } 66 | 67 | inline fun listListStructure(): TypeStructure { 68 | return TypeStructure( 69 | typeOf>>(), 70 | arrayListOf( 71 | TypeStructure(typeOf>(), arrayListOf(TypeStructure(TYPE::class.java))) 72 | ) 73 | ) 74 | } -------------------------------------------------------------------------------- /cfg4k-core/src/test/kotlin/com/jdiazcano/cfg4k/parsers/MapParserTest.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.parsers 2 | 3 | import com.jdiazcano.cfg4k.Potato 4 | import com.jdiazcano.cfg4k.core.ConfigContext 5 | import com.jdiazcano.cfg4k.json.JsonConfigLoader 6 | import com.jdiazcano.cfg4k.providers.DefaultConfigProvider 7 | import com.jdiazcano.cfg4k.sources.StringConfigSource 8 | import com.jdiazcano.cfg4k.utils.TypeStructure 9 | import com.jdiazcano.cfg4k.utils.typeOf 10 | import io.kotlintest.shouldBe 11 | import io.kotlintest.specs.StringSpec 12 | 13 | class MapParserTest: StringSpec({ 14 | val json = """ 15 | { 16 | "str": { 17 | "str": "b" 18 | }, 19 | "liststr": { 20 | "liststr": [ "a", "b" ] 21 | }, 22 | "listint": { 23 | "listint": [ 1, 2 ] 24 | }, 25 | "int": { 26 | "int": 1 27 | }, 28 | "potatolist": { 29 | "potatolist": [ 30 | { 31 | "name": "Super potato", 32 | "size": 1 33 | } 34 | ] 35 | } 36 | } 37 | """.trimIndent() 38 | val loader = JsonConfigLoader(StringConfigSource(json)) 39 | val provider = DefaultConfigProvider(loader) 40 | val context = ConfigContext(provider, "") 41 | 42 | "can parse a normal String to String map" { 43 | MapParser.parse(context, loader.get("str")!!, mapStructure()) 44 | .shouldBe(mapOf("str" to "b")) 45 | } 46 | 47 | "can parse a normal String to Integer map" { 48 | MapParser.parse(context, loader.get("int")!!, mapStructure()) 49 | .shouldBe(mapOf("int" to 1)) 50 | } 51 | 52 | "can parse a normal String to List map" { 53 | MapParser.parse(context, loader.get("liststr")!!, mapListStructure()) 54 | .shouldBe(mapOf("liststr" to listOf("a", "b"))) 55 | } 56 | 57 | "can parse a normal String to List map" { 58 | MapParser.parse(context, loader.get("listint")!!, mapListStructure()) 59 | .shouldBe(mapOf("listint" to listOf(1, 2))) 60 | } 61 | 62 | "can parse a normal String to List map" { 63 | MapParser.parse(context.copy(propertyName = "potatolist"), loader.get("potatolist")!!, mapListStructure()) 64 | .shouldBe(mapOf("potatolist" to listOf(Potato("Super potato", 1)))) 65 | } 66 | }) 67 | 68 | inline fun mapStructure(): TypeStructure { 69 | return TypeStructure( 70 | typeOf>(), 71 | arrayListOf( 72 | TypeStructure(FROM::class.java), 73 | TypeStructure(TO::class.java) 74 | ) 75 | ) 76 | } 77 | 78 | inline fun mapListStructure(): TypeStructure { 79 | return TypeStructure( 80 | typeOf>(), 81 | arrayListOf( 82 | TypeStructure(FROM::class.java), 83 | TypeStructure(typeOf>(), arrayListOf(TypeStructure(TO::class.java))) 84 | ) 85 | ) 86 | } -------------------------------------------------------------------------------- /cfg4k-core/src/test/kotlin/com/jdiazcano/cfg4k/parsers/ParsersTest.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.parsers 2 | 3 | import com.jdiazcano.cfg4k.core.ConfigContext 4 | import com.jdiazcano.cfg4k.core.ConfigObject 5 | import com.jdiazcano.cfg4k.parsers.Parsers.findParser 6 | import com.jdiazcano.cfg4k.parsers.Parsers.isExtendedParseable 7 | import com.jdiazcano.cfg4k.utils.ParserClassNotFound 8 | import com.jdiazcano.cfg4k.utils.TypeStructure 9 | import io.kotlintest.matchers.boolean.shouldBeTrue 10 | import io.kotlintest.matchers.types.shouldNotBeNull 11 | import io.kotlintest.shouldThrow 12 | import io.kotlintest.specs.StringSpec 13 | import kotlin.test.assertFailsWith 14 | 15 | class ParsersTest: StringSpec({ 16 | 17 | "throws exception if parser isn't found" { 18 | shouldThrow { 19 | StringSpec::class.java.findParser() 20 | } 21 | } 22 | 23 | "can find an enum parser" { 24 | EnumType::class.java.findParser().shouldNotBeNull() 25 | } 26 | 27 | "can find the parser for a parseable class" { 28 | Int::class.java.findParser().shouldNotBeNull() 29 | } 30 | 31 | "extended parseable returns true for maps" { 32 | Map::class.java.isExtendedParseable().shouldBeTrue() 33 | } 34 | 35 | "extended parseable returns true for lists" { 36 | List::class.java.isExtendedParseable().shouldBeTrue() 37 | } 38 | 39 | "extended parseable returns true for collection" { 40 | Collection::class.java.isExtendedParseable().shouldBeTrue() 41 | } 42 | 43 | "adding a parser will make it parseable" { 44 | shouldThrow { 45 | ParsersTest::class.java.findParser() 46 | } 47 | 48 | Parsers.addParser(ParsersTest::class.java, ParsersTestParser) 49 | 50 | ParsersTest::class.java.let { 51 | it.findParser().shouldNotBeNull() 52 | it.isExtendedParseable().shouldBeTrue() 53 | } 54 | } 55 | 56 | }) 57 | 58 | object ParsersTestParser: Parser { 59 | override fun parse(context: ConfigContext, value: ConfigObject, typeStructure: TypeStructure): ParsersTest { 60 | throw IllegalStateException() 61 | } 62 | } -------------------------------------------------------------------------------- /cfg4k-core/src/test/kotlin/com/jdiazcano/cfg4k/parsers/PrimiteParsersTest.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.parsers 2 | 3 | import com.jdiazcano.cfg4k.core.ConfigContext 4 | import com.jdiazcano.cfg4k.core.toConfig 5 | import io.kotlintest.matchers.boolean.shouldBeTrue 6 | import io.kotlintest.shouldBe 7 | import io.kotlintest.specs.StringSpec 8 | import io.mockk.mockk 9 | 10 | class PrimiteParsersTest: StringSpec({ 11 | val context = mockk() 12 | 13 | "IntParser parses properly" { 14 | IntParser.parse(context, "1".toConfig()).shouldBe(1) 15 | } 16 | 17 | "LongParser parses properly" { 18 | LongParser.parse(context, "1".toConfig()).shouldBe(1L) 19 | } 20 | 21 | "ShortParser parses properly" { 22 | ShortParser.parse(context, "1".toConfig()).shouldBe(1.toShort()) 23 | } 24 | 25 | "BooleanParser parses properly" { 26 | BooleanParser.parse(context, "true".toConfig()).shouldBeTrue() 27 | } 28 | 29 | "FloatParser parses properly" { 30 | FloatParser.parse(context, "1.1".toConfig()).shouldBe(1.1F) 31 | } 32 | 33 | "DoubleParser parses properly" { 34 | DoubleParser.parse(context, "12.11".toConfig()).shouldBe(12.11) 35 | } 36 | 37 | "ByteParser parses properly" { 38 | ByteParser.parse(context, "13".toConfig()).shouldBe(13.toByte()) 39 | } 40 | 41 | "StringParser parses properly" { 42 | StringParser.parse(context, "asdf".toConfig()).shouldBe("asdf") 43 | } 44 | 45 | "ClassParser parses properly" { 46 | ClassParser.parse(context, "io.kotlintest.specs.StringSpec".toConfig()).shouldBe(StringSpec::class.java) 47 | } 48 | 49 | }) -------------------------------------------------------------------------------- /cfg4k-core/src/test/kotlin/com/jdiazcano/cfg4k/providers/OverrideConfigProviderTest.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.providers 2 | 3 | import com.jdiazcano.cfg4k.loaders.PropertyConfigLoader 4 | import com.jdiazcano.cfg4k.sources.StringConfigSource 5 | import io.kotlintest.shouldBe 6 | import io.kotlintest.specs.StringSpec 7 | 8 | class OverrideConfigProviderTest: StringSpec({ 9 | val source1 = StringConfigSource("a=1\nb=1\nc=1") 10 | val source2 = StringConfigSource("a=2\nb=2\nd=2") 11 | 12 | val provider1 = DefaultConfigProvider(PropertyConfigLoader(source1)) 13 | val provider2 = DefaultConfigProvider(PropertyConfigLoader(source2)) 14 | 15 | val provider = OverrideConfigProvider(provider1, provider2) 16 | 17 | "the provider is not overriden for 'a'" { 18 | provider.get("a") shouldBe 1 19 | } 20 | 21 | "the provider is not overriden for 'b'" { 22 | provider.get("b") shouldBe 1 23 | } 24 | 25 | "the provider is not overriden for 'c'" { 26 | provider.get("c") shouldBe 1 27 | } 28 | 29 | "the provider is overriden for 'd'" { 30 | provider.get("d") shouldBe 2 31 | } 32 | }) -------------------------------------------------------------------------------- /cfg4k-core/src/test/kotlin/com/jdiazcano/cfg4k/sources/ClasspathConfigSourceTest.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.sources 2 | 3 | import io.kotlintest.matchers.types.shouldNotBeNull 4 | import io.kotlintest.specs.StringSpec 5 | 6 | class ClasspathConfigSourceTest: StringSpec({ 7 | 8 | "the stream from the source should not be null" { 9 | val source = ClasspathConfigSource("/test.properties") 10 | source.read().shouldNotBeNull() 11 | } 12 | 13 | }) -------------------------------------------------------------------------------- /cfg4k-core/src/test/kotlin/com/jdiazcano/cfg4k/sources/CommonConfigSourcesTest.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.sources 2 | 3 | import io.kotlintest.shouldBe 4 | import io.kotlintest.specs.FeatureSpec 5 | 6 | class CommonConfigSourcesTest: FeatureSpec({ 7 | feature("StringConfigSource") { 8 | val source = StringConfigSource("a=1") 9 | scenario("the stream and the string are the same") { 10 | source.read().readBytes().toString(Charsets.UTF_8).shouldBe("a=1") 11 | } 12 | } 13 | 14 | feature("StringRotationConfigSource") { 15 | val source = StringRotationConfigSource(listOf( 16 | "a=1", 17 | "a=2" 18 | )) 19 | 20 | scenario("the first string is good") { 21 | source.read().readBytes().toString(Charsets.UTF_8).shouldBe("a=1") 22 | } 23 | 24 | scenario("second string too!") { 25 | source.read().readBytes().toString(Charsets.UTF_8).shouldBe("a=2") 26 | } 27 | 28 | scenario("it goes back to the first when overflow") { 29 | source.read().readBytes().toString(Charsets.UTF_8).shouldBe("a=1") 30 | } 31 | } 32 | 33 | feature("FunctionConfigSource") { 34 | val source = FunctionConfigSource { "a=1".toByteArray() } 35 | 36 | scenario("the content is the same") { 37 | source.read().readBytes().toString(Charsets.UTF_8).shouldBe("a=1") 38 | } 39 | } 40 | 41 | feature("StringFunctionConfigSource") { 42 | val source = StringFunctionConfigSource { "a=1" } 43 | 44 | scenario("the content is the same") { 45 | source.read().readBytes().toString(Charsets.UTF_8).shouldBe("a=1") 46 | } 47 | } 48 | }) -------------------------------------------------------------------------------- /cfg4k-core/src/test/kotlin/com/jdiazcano/cfg4k/sources/ConfigSourceTest.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.sources 2 | 3 | import io.kotlintest.matchers.types.shouldNotBeNull 4 | import io.kotlintest.specs.StringSpec 5 | 6 | class ConfigSourceTest: StringSpec({ 7 | 8 | "the stream from the source should not be null" { 9 | val source = ClasspathConfigSource("/test.properties") 10 | source.read().toConfigSource().read().shouldNotBeNull() 11 | } 12 | 13 | }) -------------------------------------------------------------------------------- /cfg4k-core/src/test/kotlin/com/jdiazcano/cfg4k/sources/FileConfigSourceTest.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.sources 2 | 3 | import io.kotlintest.matchers.types.shouldNotBeNull 4 | import io.kotlintest.specs.StringSpec 5 | import java.io.File 6 | import java.nio.file.Paths 7 | 8 | class FileConfigSourceTest: StringSpec({ 9 | 10 | "the stream from the source should not be null with a file" { 11 | val source = FileConfigSource(File("src/test/resources/test.properties")) 12 | source.read().shouldNotBeNull() 13 | } 14 | 15 | "the stream from the source should not be null with a path" { 16 | val source = FileConfigSource(Paths.get("src/test/resources/test.properties")) 17 | source.read().shouldNotBeNull() 18 | } 19 | 20 | }) -------------------------------------------------------------------------------- /cfg4k-core/src/test/kotlin/com/jdiazcano/cfg4k/sources/URLConfigSourceTest.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.sources 2 | 3 | import com.jdiazcano.cfg4k.loaders.PropertyConfigLoader 4 | import com.jdiazcano.cfg4k.providers.DefaultConfigProvider 5 | import com.jdiazcano.cfg4k.providers.get 6 | import io.kotlintest.matchers.types.shouldBeNull 7 | import io.kotlintest.matchers.types.shouldNotBeNull 8 | import io.kotlintest.shouldBe 9 | import io.kotlintest.specs.FeatureSpec 10 | import io.kotlintest.specs.StringSpec 11 | import okhttp3.mockwebserver.MockResponse 12 | import okhttp3.mockwebserver.MockWebServer 13 | import java.net.URL 14 | import java.util.concurrent.TimeUnit 15 | 16 | private const val AUTH_HEADER = "Authorization" 17 | 18 | class URLConfigSourceTest: FeatureSpec({ 19 | 20 | feature("an url config source") { 21 | scenario("the stream should not be null") { 22 | val source = URLConfigSource(URLConfigSourceTest::class.java.getResource("/test.properties")) 23 | source.read().shouldNotBeNull() 24 | } 25 | } 26 | 27 | feature("config source fetching data from a URL") { 28 | val configSource = "a: b" 29 | val configPath = "/path" 30 | val server = MockWebServer().apply { start() } 31 | val configUrl = server.url(configPath).toUrl() 32 | 33 | scenario("should fetch config") { 34 | val source = URLConfigSource(configUrl) 35 | server.enqueue(MockResponse().setBody(configSource)) 36 | 37 | source.read().bufferedReader().useLines { 38 | val fetchedConfigSource = it.toList().single() 39 | fetchedConfigSource.shouldBe(configSource) 40 | 41 | val request = server.takeRequest(1, TimeUnit.MILLISECONDS)!! 42 | request.method shouldBe "GET" 43 | request.path shouldBe configPath 44 | } 45 | } 46 | 47 | scenario("should fetch config without authentication") { 48 | val source = URLConfigSource(configUrl) 49 | server.enqueue(MockResponse().setBody(configSource)) 50 | // Calling use to auto-close the stream 51 | source.read().use { 52 | val request = server.takeRequest(1, TimeUnit.MILLISECONDS)!! 53 | request.getHeader(AUTH_HEADER).shouldBeNull() 54 | } 55 | } 56 | 57 | scenario("should fetch config with provided authentication header") { 58 | val authHeader = "some-header" 59 | val source = URLConfigSource(configUrl, authHeader) 60 | server.enqueue(MockResponse().setBody(configSource)) 61 | // Calling use to auto-close the stream 62 | source.read().use { 63 | val request = server.takeRequest(1, TimeUnit.MILLISECONDS)!! 64 | request.getHeader(AUTH_HEADER).shouldBe(authHeader) 65 | } 66 | } 67 | 68 | server.shutdown() 69 | 70 | } 71 | 72 | feature("a github config source") { 73 | scenario("should fetch from a public repo") { 74 | val configSource = GitHubConfigSource("jdiazcano", "cfg4k", "cfg4k-core/src/test/resources/test.properties") 75 | val provider = DefaultConfigProvider(PropertyConfigLoader(configSource)) 76 | provider.get("a").shouldBe("b") 77 | } 78 | } 79 | 80 | feature("authentication header generators") { 81 | scenario("basic authentication generator") { 82 | basicAuth("", "").shouldBe("Basic Og==") 83 | basicAuth("foo", "bar").shouldBe("Basic Zm9vOmJhcg==") 84 | basicAuth("username", "password").shouldBe("Basic dXNlcm5hbWU6cGFzc3dvcmQ=") 85 | } 86 | } 87 | 88 | feature("token header generators") { 89 | scenario("token authentication generator") { 90 | "mycooltoken".asToken().shouldBe("Token mycooltoken") 91 | } 92 | } 93 | 94 | feature("bitbucket config source") { 95 | scenario("the url is the one expected") { 96 | val source = BitbucketConfigSource("owner", "slug", "path/config.json") as URLConfigSource 97 | source.url.toString() shouldBe "https://api.bitbucket.org/2.0/repositories/owner/slug/src/master/path/config.json" 98 | } 99 | 100 | scenario("the url is the one expected with username and pass") { 101 | val source = BitbucketUserConfigSource("user", "password","owner", "slug", "path/config.json") as URLConfigSource 102 | source.url.toString() shouldBe "https://api.bitbucket.org/2.0/repositories/owner/slug/src/master/path/config.json" 103 | } 104 | } 105 | 106 | feature("github config source") { 107 | scenario("the url is the one expected") { 108 | val source = GitHubConfigSource("owner", "slug", "path/config.json") as URLConfigSource 109 | source.url.toString() shouldBe "https://raw.githubusercontent.com/owner/slug/master/path/config.json" 110 | } 111 | } 112 | 113 | }) -------------------------------------------------------------------------------- /cfg4k-core/src/test/kotlin/com/jdiazcano/cfg4k/utils/TypableTests.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 Javier Díaz-Cano Martín-Albo (javierdiazcanom@gmail.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jdiazcano.cfg4k.utils 18 | 19 | import io.kotlintest.shouldBe 20 | import io.kotlintest.specs.StringSpec 21 | 22 | class TypableTests : StringSpec({ 23 | "Testing the toString of Typable" { 24 | typeOf>().toString().shouldBe("java.util.List") 25 | typeOf>().toString().shouldBe("java.util.List") 26 | typeOf().toString().shouldBe("class java.lang.Integer") 27 | typeOf>>().toString().shouldBe("java.util.Map>") 28 | } 29 | }) -------------------------------------------------------------------------------- /cfg4k-core/src/test/kotlin/com/jdiazcano/cfg4k/utils/TypeConverterTest.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.utils 2 | 3 | import io.kotlintest.shouldBe 4 | import io.kotlintest.specs.StringSpec 5 | 6 | class TypeConverterTest: StringSpec({ 7 | 8 | "should convert a simple class" { 9 | typeOf().convert().shouldBe(TypeStructure( 10 | typeOf() 11 | )) 12 | } 13 | 14 | "should convert a simple list" { 15 | typeOf>().convert().shouldBe(TypeStructure( 16 | typeOf>(), 17 | arrayListOf(TypeStructure(Integer::class.java)) 18 | )) 19 | } 20 | 21 | "should convert list within lists" { 22 | typeOf>>().convert().shouldBe(TypeStructure( 23 | typeOf>>(), 24 | arrayListOf(TypeStructure( 25 | typeOf>(), 26 | arrayListOf(TypeStructure(Integer::class.java)) 27 | )) 28 | )) 29 | } 30 | 31 | "should convert a map" { 32 | typeOf, Int>>().convert().shouldBe(TypeStructure( 33 | typeOf, Int>>(), 34 | arrayListOf( 35 | TypeStructure(typeOf>(), arrayListOf(TypeStructure(String::class.java))), 36 | TypeStructure(Integer::class.java) 37 | ) 38 | )) 39 | } 40 | 41 | "should convert nested maps and lists" { 42 | typeOf, Int>>>().convert().shouldBe(TypeStructure( 43 | typeOf, Int>>>(), 44 | arrayListOf(TypeStructure( 45 | typeOf, Int>>(), 46 | arrayListOf( 47 | TypeStructure(typeOf>(), arrayListOf(TypeStructure(String::class.java))), 48 | TypeStructure(Integer::class.java) 49 | ) 50 | )) 51 | )) 52 | } 53 | 54 | }) -------------------------------------------------------------------------------- /cfg4k-core/src/test/resources/book.properties: -------------------------------------------------------------------------------- 1 | harrypotter.id=1 2 | harrypotter.title=Prisoner of azkaban -------------------------------------------------------------------------------- /cfg4k-core/src/test/resources/classedparser.properties: -------------------------------------------------------------------------------- 1 | persons.me.name=Javi 2 | persons.me.age=26 3 | persons.peter.name=Peter 4 | persons.peter.age=20 -------------------------------------------------------------------------------- /cfg4k-core/src/test/resources/doubledproperty.properties: -------------------------------------------------------------------------------- 1 | a.b=2 2 | a.c=2 3 | a.d=2 4 | a=1 5 | a.e=2 6 | a.f.g=2 7 | a.a.a.a=2 -------------------------------------------------------------------------------- /cfg4k-core/src/test/resources/enumtest.properties: -------------------------------------------------------------------------------- 1 | thisWillBeEnum=TEST 2 | prefixed.enumtest=TEST2 3 | another.prefixed.double.enumtest=TEST1 -------------------------------------------------------------------------------- /cfg4k-core/src/test/resources/first.properties: -------------------------------------------------------------------------------- 1 | int=1 2 | pepe=2 -------------------------------------------------------------------------------- /cfg4k-core/src/test/resources/listtest.properties: -------------------------------------------------------------------------------- 1 | list=1,2,3,4,5,6,7 2 | set=1,2,3,4,5,6,7 3 | array=1,2,3,4,5,6,7 4 | enumerito=A,B 5 | prefixed.set=1,2,3,4,5,6,7 6 | prefixed.array=1,2,3,4,5,6,7 7 | prefixed.list=1,2,3,4,5,6,7 8 | prefixed.enumerito=A,B 9 | -------------------------------------------------------------------------------- /cfg4k-core/src/test/resources/nestedtest.properties: -------------------------------------------------------------------------------- 1 | nested.a=b 2 | nested.c=d 3 | nested.integerProperty=1 4 | nested.longProperty=2 5 | nested.shortProperty=1 6 | nested.doubleProperty=1.1 7 | nested.floatProperty=2.1 8 | nested.byteProperty=2 9 | normal=2 -------------------------------------------------------------------------------- /cfg4k-core/src/test/resources/nulltest.properties: -------------------------------------------------------------------------------- 1 | valueNonNullable.integerProperty=1 -------------------------------------------------------------------------------- /cfg4k-core/src/test/resources/overridetest.properties: -------------------------------------------------------------------------------- 1 | a=overrideb 2 | c=overrided -------------------------------------------------------------------------------- /cfg4k-core/src/test/resources/overridingnulltest.properties: -------------------------------------------------------------------------------- 1 | valueNonNullable.integerProperty=2 -------------------------------------------------------------------------------- /cfg4k-core/src/test/resources/second.properties: -------------------------------------------------------------------------------- 1 | paco=2 2 | pico=Test -------------------------------------------------------------------------------- /cfg4k-core/src/test/resources/supernestedtest.properties: -------------------------------------------------------------------------------- 1 | supernested.nested.a=b 2 | supernested.nested.c=d 3 | supernested.nested.integerProperty=1 4 | supernested.nested.longProperty=2 5 | supernested.nested.shortProperty=1 6 | supernested.nested.doubleProperty=1.1 7 | supernested.nested.floatProperty=2.1 8 | supernested.nested.byteProperty=2 9 | supernested.normal=2 10 | normal=3 -------------------------------------------------------------------------------- /cfg4k-core/src/test/resources/test.properties: -------------------------------------------------------------------------------- 1 | a=b 2 | c=d 3 | integerProperty=1 4 | longProperty=2 5 | shortProperty=1 6 | doubleProperty=1.1 7 | floatProperty=2.1 8 | byteProperty=2 9 | booleanProperty=true 10 | bigIntegerProperty=1 11 | bigDecimalProperty=1.1 12 | toString=this should not be ever used 13 | dateProperty=01-01-2017 14 | calendarProperty=01-01-2017 15 | localDateProperty=01-01-2017 16 | isoLocalDateProperty=2017-01-01 17 | localDateTimeProperty=01-01-2017 18:01:31 18 | isoLocalDateTimeProperty=2017-01-01T18:01:31 19 | zonedDateTimeProperty=01-01-2017 18:01:31 20 | isoZonedDateTimeProperty=2017-01-01T18:01:31+01:00 21 | offsetDateTimeProperty=01-01-2017 18:01:31+01:00 22 | isoOffsetDateTimeProperty=2017-01-01T18:01:31+01:00 23 | offsetTimeProperty=18:01:31+01:00 24 | isoOffsetTimeProperty=18:01:31+01:00 25 | url=https://www.amazon.com 26 | uri=https://www.amazon.com 27 | path=mypath.txt 28 | file=myfile.txt 29 | cool.nottest=1 30 | cool.another=wow 31 | nested.normal=1 32 | nested.supernested.normal=2 -------------------------------------------------------------------------------- /cfg4k-git/README.md: -------------------------------------------------------------------------------- 1 | # Git config loader 2 | This git config loader will read from a public or private repository and use the FileLoader that you provide to it in 3 | order to load all the settings in memory. 4 | 5 | ## Parameters 6 | |Name|Type|Required|Default value| 7 | |---|---|---|---| 8 | |uri|String|Yes| | 9 | |repoDirectory|File|Yes| | 10 | |configFilePath|String|Yes| | 11 | |branch|String|No|master| 12 | |credentials|CredentialsProvider|No|null| 13 | |ssh|CustomConfigSessionFactory|No|null| 14 | 15 | ## Public auth 16 | Nothing to configure! Just write the uri, folder, the file and optionally, the branch. 17 | 18 | ## User auth 19 | If you want to use your repository with https and user/password authentication you have to pass the 20 | `CredentialsProvider (credentials)` as: 21 | ```kotlin 22 | val credentials = UsernamePasswordCredentialsProvider("myuser", "mypassword") 23 | ``` 24 | 25 | ## Ssh auth 26 | To achieve this you have to instantiate a CustomConfigSessionFactory. 27 | This config factory will take two parameters (one optional) as the ssh key and the known hosts file: 28 | ```kotlin 29 | val ssh = CustomConfigSessionFactory( 30 | System.getProperty("user.home") + "/.ssh/id_rsa", 31 | System.getProperty("user.home") + "/.ssh/known_hosts" 32 | ) 33 | ``` 34 | 35 | ## Example 36 | ```kotlin 37 | val loader = GitConfigLoader( 38 | "https://github.com/jdiazcano/cfg4k-git-test.git", // (1) Repo url 39 | File("cfg4k-git-test"), // (2) Folder to be used to clone 40 | "test.json", // (3) Settings file 41 | loaderGenerator = ::JsonConfigLoader // (4) The loader used to load the (3) settings file 42 | ) 43 | ``` -------------------------------------------------------------------------------- /cfg4k-git/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: '../gradle/versions.gradle' 2 | 3 | dependencies { 4 | compile project(":cfg4k-core") 5 | compile libraries.jetbrains.kotlin.stdlib 6 | compile libraries.eclipse.jgit 7 | 8 | testCompile libraries.jetbrains.spek.api 9 | testCompile libraries.junitrunner 10 | testCompile libraries.expekt 11 | testRuntime libraries.jetbrains.spek.engine 12 | } -------------------------------------------------------------------------------- /cfg4k-git/src/main/kotlin/com/jdiazcano/cfg4k/loaders/git/CustomConfigSessionFactory.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.loaders.git 2 | 3 | import com.jcraft.jsch.JSch 4 | import com.jcraft.jsch.JSchException 5 | import com.jcraft.jsch.Session 6 | import org.eclipse.jgit.transport.JschConfigSessionFactory 7 | import org.eclipse.jgit.transport.OpenSshConfig 8 | import org.eclipse.jgit.util.FS 9 | 10 | open class CustomConfigSessionFactory( 11 | val keyPath: String, 12 | val knownHostsFile: String? = null 13 | ) : JschConfigSessionFactory() { 14 | 15 | override fun configure(hc: OpenSshConfig.Host?, session: Session?) { 16 | if (knownHostsFile == null) { 17 | session?.setConfig("StrictHostKeyChecking", "no") 18 | } 19 | } 20 | 21 | @Throws(JSchException::class) 22 | override fun getJSch(hc: OpenSshConfig.Host, fs: FS): JSch { 23 | val jsch = super.getJSch(hc, fs) 24 | jsch.removeAllIdentity() 25 | jsch.addIdentity(keyPath) 26 | knownHostsFile?.let { jsch.setKnownHosts(it) } 27 | return jsch 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /cfg4k-git/src/main/kotlin/com/jdiazcano/cfg4k/loaders/git/GitConfigLoader.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.loaders.git 2 | 3 | import com.jdiazcano.cfg4k.core.ConfigObject 4 | import com.jdiazcano.cfg4k.loaders.ConfigLoader 5 | import com.jdiazcano.cfg4k.loaders.PropertyConfigLoader 6 | import com.jdiazcano.cfg4k.sources.ConfigSource 7 | import com.jdiazcano.cfg4k.sources.FileConfigSource 8 | import com.jdiazcano.cfg4k.utils.SettingsNotInitialisedException 9 | import org.eclipse.jgit.api.Git 10 | import org.eclipse.jgit.transport.CredentialsProvider 11 | import org.eclipse.jgit.transport.SshSessionFactory 12 | import org.eclipse.jgit.transport.SshTransport 13 | import java.io.Closeable 14 | import java.io.File 15 | 16 | /** 17 | * Git config loader. This loader will connect to a git repository (public or private), clone it and use these files 18 | * to configure the loader inside it. This will only act as a bridge for cloning the repo and you must provide the 19 | * config loader generator (which is only a reference to a normal loader constructor). 20 | * 21 | * This resource can (and should) be closed but if it is not closed and you use the same directory it will open the git 22 | * repository instead of trying to clone it again 23 | */ 24 | class GitConfigSource( 25 | uri: String, 26 | private val repoDirectory: File, 27 | private val configFilePath: String, 28 | branch: String = "master", 29 | private val credentials: CredentialsProvider? = null, 30 | private val ssh: CustomConfigSessionFactory? = null, 31 | private val loaderGenerator: (ConfigSource) -> ConfigLoader = ::PropertyConfigLoader 32 | ) : ConfigLoader, Closeable { 33 | 34 | private val clonedRepo: Git 35 | private var initialised = false 36 | private lateinit var loader: ConfigLoader 37 | 38 | init { 39 | // If the repo exist we just open the directory 40 | if (repoDirectory.exists() && repoDirectory.resolve(".git").exists()) { 41 | clonedRepo = Git.open(repoDirectory) 42 | } else { 43 | repoDirectory.deleteRecursively() 44 | repoDirectory.mkdirs() 45 | val builder = Git.cloneRepository() 46 | .setURI(uri) 47 | .setBranch(branch) 48 | .setDirectory(repoDirectory) 49 | 50 | if (credentials != null) { 51 | builder.setCredentialsProvider(credentials) 52 | } else if (ssh != null) { 53 | builder.setTransportConfigCallback { transport -> 54 | when (transport) { 55 | is SshTransport -> transport.sshSessionFactory = ssh 56 | } 57 | } 58 | } 59 | 60 | clonedRepo = builder.call() 61 | } 62 | initProperties() 63 | } 64 | 65 | private fun initProperties() { 66 | // This is needed to set the instance of the SshSessionFactory if there is no known hosts file because otherwise 67 | // the StrictHostKeyChecking property must be set to false in order to continue with the clone 68 | if (ssh != null && ssh.knownHostsFile == null) { 69 | SshSessionFactory.setInstance(ssh) 70 | } 71 | 72 | clonedRepo.pull().setCredentialsProvider(credentials).call() 73 | loader = loaderGenerator(FileConfigSource(repoDirectory.resolve(configFilePath))) 74 | 75 | initialised = true 76 | } 77 | 78 | override fun get(key: String): ConfigObject? { 79 | if (!initialised) { 80 | throw SettingsNotInitialisedException("Settings have not been initialised. Git repository is being cloned") 81 | } 82 | 83 | return loader.get(key) 84 | } 85 | 86 | override fun reload() { 87 | initProperties() 88 | } 89 | 90 | override fun close() { 91 | clonedRepo.close() 92 | repoDirectory.deleteRecursively() 93 | } 94 | } -------------------------------------------------------------------------------- /cfg4k-git/src/test/kotlin/GitConfigLoaderTest.kt: -------------------------------------------------------------------------------- 1 | 2 | import com.jdiazcano.cfg4k.core.toConfig 3 | import com.jdiazcano.cfg4k.loaders.PropertyConfigLoader 4 | import com.jdiazcano.cfg4k.loaders.git.GitConfigSource 5 | import com.winterbe.expekt.should 6 | import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider 7 | import org.jetbrains.spek.api.Spek 8 | import org.jetbrains.spek.api.dsl.describe 9 | import org.jetbrains.spek.api.dsl.it 10 | import java.io.File 11 | 12 | class GitConfigSourceTest : Spek({ 13 | 14 | val isRunninInTravis = System.getenv()["CI_NAME"] ?: "" == "travis-ci" 15 | val gitUser = System.getenv("CFGK_GIT_USER") 16 | val gitPassword = System.getenv("CFGK_GIT_PASS") 17 | 18 | describe("a git config loader 1") { 19 | 20 | it("should load the integer property") { 21 | val repoDirectory = File("publictest") 22 | val source = GitConfigSource( 23 | "https://github.com/jdiazcano/cfg4k-git-test.git", 24 | repoDirectory, 25 | "test.properties", 26 | loaderGenerator = ::PropertyConfigLoader 27 | ) 28 | testSource(source) 29 | repoDirectory.deleteRecursively() 30 | } 31 | 32 | } 33 | 34 | 35 | describe("a git config loader 2") { 36 | 37 | it("should load the integer property") { 38 | if (isRunninInTravis) { 39 | val repoDirectory = File("userpasstest") 40 | val source = GitConfigSource( 41 | "https://bitbucket.org/javierdiaz/cfg4k-git-test.git", 42 | repoDirectory, 43 | "test.properties", 44 | loaderGenerator = ::PropertyConfigLoader, 45 | credentials = UsernamePasswordCredentialsProvider(gitUser, gitPassword) 46 | ) 47 | testSource(source) 48 | repoDirectory.deleteRecursively() 49 | } 50 | } 51 | } 52 | 53 | // describe("a git config loader 3") { 54 | // 55 | // it("should load the integer property") { 56 | // if (isRunninInTravis) { 57 | // val repoDirectory = File("sshtest") 58 | // val source = GitConfigSource( 59 | // "git@bitbucket.org:javierdiaz/cfg4k-git-test.git", 60 | // repoDirectory, 61 | // "test.properties", 62 | // loaderGenerator = ::PropertyConfigLoader, 63 | // ssh = CustomConfigSessionFactory(System.getProperty("user.home") + "/.ssh/id_rsa") 64 | // ) 65 | // testSource(source) 66 | // repoDirectory.deleteRecursively() 67 | // } 68 | // } 69 | // 70 | // } 71 | // describe("a git config loader 4") { 72 | // 73 | // it("should load the integer property") { 74 | // if (isRunninInTravis) { 75 | // val repoDirectory = File("sshknownhosttest") 76 | // val source = GitConfigSource( 77 | // "git@bitbucket.org:javierdiaz/cfg4k-git-test.git", 78 | // repoDirectory, 79 | // "test.properties", 80 | // loaderGenerator = ::PropertyConfigLoader, 81 | // ssh = CustomConfigSessionFactory(System.getProperty("user.home") + "/.ssh/id_rsa", System.getProperty("user.home") + "/.ssh/known_hosts") 82 | // ) 83 | // testSource(source) 84 | // repoDirectory.deleteRecursively() 85 | // } 86 | // } 87 | // } 88 | }) 89 | 90 | private fun testSource(loader: GitConfigSource) { 91 | // There is no point on testing more properties because if one is loaded, the rest will be loaded too 92 | // everything is tested in the providers anyways so these loaders are all ok. 93 | loader.get("integerProperty").should.be.equal("1".toConfig()) 94 | loader.close() 95 | } -------------------------------------------------------------------------------- /cfg4k-git/src/test/kotlin/GitConfigProviderTest.kt: -------------------------------------------------------------------------------- 1 | 2 | import com.jdiazcano.cfg4k.loaders.git.CustomConfigSessionFactory 3 | import com.jdiazcano.cfg4k.providers.ConfigProvider 4 | import com.jdiazcano.cfg4k.providers.get 5 | import com.winterbe.expekt.should 6 | import org.eclipse.jgit.api.Git 7 | import org.eclipse.jgit.transport.SshTransport 8 | import org.jetbrains.spek.api.Spek 9 | import java.io.File 10 | 11 | class GitConfigProviderTest : Spek({ 12 | 13 | val isRunninInTravis = System.getenv()["CI_NAME"] ?: "" == "travis-ci" 14 | val reloadFolder = File("gitreloadtest") 15 | 16 | // describe("a git config loader 4") { 17 | // 18 | // it("should load the integer property") { 19 | // if (isRunninInTravis) { 20 | // val loader = GitConfigSource( 21 | // "git@bitbucket.org:javierdiaz/cfg4k-git-test.git", 22 | // reloadFolder, 23 | // "test.properties", 24 | // loaderGenerator = ::PropertyConfigLoader, 25 | // ssh = CustomConfigSessionFactory(System.getProperty("user.home") + "/.ssh/id_rsa", System.getProperty("user.home") + "/.ssh/known_hosts") 26 | // ) 27 | // val provider = DefaultConfigProvider(loader, TimedReloadStrategy(1, TimeUnit.SECONDS)) 28 | // testProvider(provider) 29 | // } 30 | // } 31 | // } 32 | 33 | afterGroup { 34 | reloadFolder.deleteRecursively() 35 | } 36 | }) 37 | 38 | private fun testProvider(provider: ConfigProvider) { 39 | // There is no point on testing more properties because if one is loaded, the rest will be loaded too 40 | // everything is tested in the providers anyways so these loaders are all ok. 41 | provider.get("integerProperty").should.be.equal(1) 42 | 43 | val reloadFolder2 = File("gitreloadtest2") 44 | reloadFolder2.mkdirs() 45 | val builder = Git.cloneRepository() 46 | .setURI("git@bitbucket.org:javierdiaz/cfg4k-git-test.git") 47 | .setBranch("master") 48 | .setDirectory(reloadFolder2) 49 | .setTransportConfigCallback { transport -> 50 | when (transport) { 51 | is SshTransport -> transport.sshSessionFactory = CustomConfigSessionFactory(System.getProperty("user.home") + "/.ssh/id_rsa", System.getProperty("user.home") + "/.ssh/known_hosts") 52 | } 53 | } 54 | 55 | val clonedRepo = builder.call() 56 | reloadFolder2.resolve("test.properties").writeText(updatedProperties) 57 | clonedRepo.add().addFilepattern("test.properties").call() 58 | clonedRepo.commit().setMessage("Updated properties with millis $millis").call() 59 | clonedRepo.push().call() 60 | Thread.sleep(5000) 61 | provider.get("longProperty").should.be.equal(millis) 62 | 63 | reloadFolder2.deleteRecursively() 64 | 65 | } 66 | private var millis = System.currentTimeMillis() 67 | private val updatedProperties = """a=b 68 | c=d 69 | integerProperty=1 70 | longProperty=$millis 71 | shortProperty=1 72 | doubleProperty=1.1 73 | floatProperty=2.1 74 | byteProperty=2 75 | list=1,2,3 76 | floatList=1.2,2.2,3.2 77 | booleanProperty=true 78 | enumList=A,B 79 | bigIntegerProperty=1 80 | bigDecimalProperty=1.1 81 | toString=this should not be ever used 82 | dateProperty=01-01-2017 83 | calendarProperty=01-01-2017 84 | localDateProperty=01-01-2017 85 | isoLocalDateProperty=2017-01-01 86 | localDateTimeProperty=01-01-2017 18:01:31 87 | isoLocalDateTimeProperty=2017-01-01T18:01:31 88 | zonedDateTimeProperty=01-01-2017 18:01:31 89 | isoZonedDateTimeProperty=2017-01-01T18:01:31+01:00 90 | offsetDateTimeProperty=01-01-2017 18:01:31+01:00 91 | isoOffsetDateTimeProperty=2017-01-01T18:01:31+01:00 92 | offsetTimeProperty=18:01:31+01:00 93 | isoOffsetTimeProperty=18:01:31+01:00""" -------------------------------------------------------------------------------- /cfg4k-hocon/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: '../gradle/versions.gradle' 2 | 3 | dependencies { 4 | compile project(":cfg4k-core") 5 | compile libraries.jetbrains.kotlin.stdlib 6 | compile libraries.typesafe 7 | 8 | testCompile libraries.jetbrains.spek.api 9 | testCompile libraries.junitrunner 10 | testCompile libraries.expekt 11 | testRuntime libraries.jetbrains.spek.engine 12 | } -------------------------------------------------------------------------------- /cfg4k-hocon/src/main/kotlin/com/jdiazcano/cfg4k/hocon/HoconConfigLoader.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.hocon 2 | 3 | import com.jdiazcano.cfg4k.loaders.DefaultConfigLoader 4 | import com.jdiazcano.cfg4k.sources.ConfigSource 5 | import com.typesafe.config.Config 6 | import com.typesafe.config.ConfigFactory 7 | import com.typesafe.config.ConfigParseOptions 8 | import java.io.File 9 | import java.io.InputStreamReader 10 | import java.net.URL 11 | 12 | fun HoconConfigLoader(url: URL, options: ConfigParseOptions = ConfigParseOptions.defaults()): HoconConfigLoader { 13 | val loader = { ConfigFactory.parseURL(url, options) } 14 | return HoconConfigLoader(loader) 15 | } 16 | 17 | fun HoconConfigLoader(file: File, options: ConfigParseOptions = ConfigParseOptions.defaults()): HoconConfigLoader { 18 | val loader = { ConfigFactory.parseFileAnySyntax(file, options) } 19 | return HoconConfigLoader(loader) 20 | } 21 | 22 | fun HoconConfigLoader(resource: String, options: ConfigParseOptions = ConfigParseOptions.defaults()): HoconConfigLoader { 23 | val loader = { ConfigFactory.parseResourcesAnySyntax(resource, options) } 24 | return HoconConfigLoader(loader) 25 | } 26 | 27 | fun HoconConfigLoader(source: ConfigSource, options: ConfigParseOptions = ConfigParseOptions.defaults()): HoconConfigLoader { 28 | val loader = { ConfigFactory.parseReader(InputStreamReader(source.read()), options) } 29 | return HoconConfigLoader(loader) 30 | } 31 | 32 | fun HoconConfigLoader(config: Config, loader: () -> Config = { config }): HoconConfigLoader { 33 | return HoconConfigLoader(loader) 34 | } 35 | 36 | /** 37 | * Config loader that will handle the input with the HOCON (Human-Optimized Config Object Notation) 38 | */ 39 | open class HoconConfigLoader(protected val loader: () -> Config) : DefaultConfigLoader() { 40 | 41 | init { 42 | root = loader().resolve().toConfig() 43 | } 44 | 45 | override fun reload() { 46 | root = loader().resolve().toConfig() 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /cfg4k-hocon/src/main/kotlin/com/jdiazcano/cfg4k/hocon/HoconConfigMapper.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.hocon 2 | 3 | import com.jdiazcano.cfg4k.core.ConfigObject 4 | import com.jdiazcano.cfg4k.core.ListConfigObject 5 | import com.jdiazcano.cfg4k.core.MapConfigObject 6 | import com.jdiazcano.cfg4k.core.StringConfigObject 7 | import com.jdiazcano.cfg4k.core.toConfig 8 | import com.typesafe.config.Config 9 | import com.typesafe.config.ConfigList 10 | 11 | fun Config.toConfig(): ConfigObject { 12 | return parseObject(root()) 13 | } 14 | 15 | private fun parseObject(parsed: com.typesafe.config.ConfigObject): ConfigObject { 16 | return MapConfigObject(parsed.map { (key, value) -> 17 | when (value) { 18 | is ConfigList -> key to parseArray(value) 19 | is com.typesafe.config.ConfigObject -> key to parseObject(value) 20 | else -> key to value.unwrapped()?.toString()?.toConfig() 21 | } 22 | }.toMap(hashMapOf())) 23 | } 24 | 25 | private fun parseArray(array: ConfigList): ConfigObject { 26 | return ListConfigObject(array.map { item -> 27 | when (item) { 28 | is com.typesafe.config.ConfigObject -> parseObject(item) 29 | else -> StringConfigObject(item.unwrapped().toString()) 30 | } 31 | }) 32 | } -------------------------------------------------------------------------------- /cfg4k-hocon/src/test/kotlin/com/jdiazcano/hocon/HoconConfigLoaderTest.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.hocon 2 | 3 | import com.jdiazcano.cfg4k.core.toConfig 4 | import com.jdiazcano.cfg4k.hocon.HoconConfigLoader 5 | import com.jdiazcano.cfg4k.providers.Providers 6 | import com.jdiazcano.cfg4k.providers.bind 7 | import com.typesafe.config.ConfigFactory 8 | import com.typesafe.config.ConfigParseOptions 9 | import com.winterbe.expekt.should 10 | import org.jetbrains.spek.api.Spek 11 | import org.jetbrains.spek.api.dsl.describe 12 | import org.jetbrains.spek.api.dsl.it 13 | import java.io.File 14 | 15 | class HoconConfigLoaderTest : Spek({ 16 | 17 | val loaders = listOf( 18 | HoconConfigLoader(File(javaClass.classLoader.getResource("test.properties").toURI())), 19 | HoconConfigLoader("test.properties"), 20 | HoconConfigLoader(javaClass.classLoader.getResource("test.properties")), 21 | HoconConfigLoader(ConfigFactory.parseResources("test.properties"), { ConfigFactory.parseResources("test.properties") }), 22 | HoconConfigLoader { ConfigFactory.parseResourcesAnySyntax("test.properties", ConfigParseOptions.defaults()) } 23 | ) 24 | 25 | loaders.forEachIndexed { i, loader -> 26 | describe("loader[$i]") { 27 | it("loader properties") { 28 | loader.get("integerProperty").should.be.equal("1".toConfig()) 29 | loader.get("longProperty").should.be.equal("2".toConfig()) 30 | loader.get("shortProperty").should.be.equal("1".toConfig()) 31 | loader.get("floatProperty").should.be.equal("2.1".toConfig()) 32 | loader.get("doubleProperty").should.be.equal("1.1".toConfig()) 33 | loader.get("byteProperty").should.be.equal("2".toConfig()) 34 | loader.get("booleanProperty").should.be.equal("true".toConfig()) 35 | } 36 | 37 | it("works with binding") { 38 | val provider = Providers.proxy(loader) 39 | 40 | val testBinder = provider.bind("") 41 | testBinder.booleanProperty().should.be.`true` 42 | testBinder.integerProperty().should.be.equal(1) 43 | testBinder.longProperty().should.be.equal(2) 44 | testBinder.shortProperty().should.be.equal(1) 45 | testBinder.floatProperty().should.be.equal(2.1F) 46 | testBinder.doubleProperty().should.be.equal(1.1) 47 | testBinder.byteProperty().should.be.equal(2) 48 | testBinder.a().should.be.equal("b") 49 | testBinder.c().should.be.equal("d") 50 | } 51 | } 52 | } 53 | 54 | }) 55 | 56 | interface TestBinder { 57 | fun integerProperty(): Int 58 | fun a(): String 59 | fun c(): String 60 | fun booleanProperty(): Boolean 61 | fun longProperty(): Long 62 | fun shortProperty(): Short 63 | fun doubleProperty(): Double 64 | fun floatProperty(): Float 65 | fun byteProperty(): Byte 66 | fun list(): List 67 | fun floatList(): List 68 | } 69 | -------------------------------------------------------------------------------- /cfg4k-hocon/src/test/kotlin/com/jdiazcano/hocon/HoconConfigMapperTest.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.hocon 2 | 3 | import com.jdiazcano.cfg4k.core.ListConfigObject 4 | import com.jdiazcano.cfg4k.core.toConfig 5 | import com.jdiazcano.cfg4k.hocon.toConfig 6 | import com.typesafe.config.ConfigFactory 7 | import com.typesafe.config.ConfigParseOptions 8 | import com.winterbe.expekt.should 9 | import org.jetbrains.spek.api.Spek 10 | import org.jetbrains.spek.api.dsl.describe 11 | 12 | class HoconConfigMapperTest : Spek({ 13 | describe("a hocon loader that can map") { 14 | val conf = ConfigFactory.parseResourcesAnySyntax("hocon.conf", ConfigParseOptions.defaults()).resolve() 15 | val configObject = conf.toConfig() 16 | 17 | configObject.isObject().should.be.`true` 18 | val ktor = configObject.asObject()["ktor"]!! 19 | val deployment = ktor.asObject()["deployment"]!! 20 | deployment.isObject().should.be.`true` 21 | deployment.asObject()["port"].should.be.equal(8080.toConfig()) 22 | deployment.asObject()["random"].should.be.equal(null) 23 | deployment.asObject()["watch"].should.be.equal(ListConfigObject(listOf( 24 | "hello".toConfig(), "bye".toConfig() 25 | ))) 26 | deployment.asObject()["ssl"]!!.asObject()["keyStore"].should.be.equal("goodKeyStore".toConfig()) 27 | 28 | val app = ktor.asObject()["application"]!! 29 | app.isObject().should.be.`true` 30 | app.asObject()["modules"].should.be.equal(ListConfigObject(listOf( 31 | "com.jdiazcano.hocon.KtorConfig".toConfig() 32 | ))) 33 | 34 | ktor.asObject()["deploymentPort"].should.be.equal(8080.toConfig()) 35 | } 36 | }) -------------------------------------------------------------------------------- /cfg4k-hocon/src/test/kotlin/com/jdiazcano/hocon/HoconConfigReloaderTest.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.hocon 2 | 3 | import com.jdiazcano.cfg4k.core.toConfig 4 | import com.jdiazcano.cfg4k.hocon.HoconConfigLoader 5 | import com.jdiazcano.cfg4k.sources.FileConfigSource 6 | import com.winterbe.expekt.should 7 | import org.jetbrains.spek.api.Spek 8 | import org.jetbrains.spek.api.dsl.describe 9 | import org.jetbrains.spek.api.dsl.it 10 | import java.io.File 11 | 12 | class HoconConfigReloaderTest: Spek({ 13 | describe("a reloadable properties config loader") { 14 | val file = File("reloadedfile.properties") 15 | file.createNewFile() 16 | file.writeText(""" 17 | { 18 | "a": "b", 19 | "c": "d" 20 | } 21 | """.trim()) 22 | 23 | val loader = HoconConfigLoader(FileConfigSource(file)) 24 | it("values should be equals like in the string") { 25 | loader.get("a").should.be.equal("b".toConfig()) 26 | loader.get("c").should.be.equal("d".toConfig()) 27 | } 28 | 29 | it("now we should have a reloaded values") { 30 | file.delete() 31 | file.createNewFile() 32 | file.writeText(""" 33 | { 34 | "a": "reloadedb", 35 | "c": "reloadedd" 36 | } 37 | """.trim()) 38 | 39 | loader.reload() 40 | loader.get("a").should.be.equal("reloadedb".toConfig()) 41 | loader.get("c").should.be.equal("reloadedd".toConfig()) 42 | 43 | file.delete() 44 | } 45 | } 46 | }) -------------------------------------------------------------------------------- /cfg4k-hocon/src/test/kotlin/com/jdiazcano/hocon/ProviderWithHocon.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.hocon 2 | 3 | import com.jdiazcano.cfg4k.hocon.HoconConfigLoader 4 | import com.jdiazcano.cfg4k.providers.Providers 5 | import com.jdiazcano.cfg4k.providers.bind 6 | import com.winterbe.expekt.should 7 | import org.jetbrains.spek.api.Spek 8 | import org.jetbrains.spek.api.dsl.describe 9 | 10 | class ProviderWithHocon : Spek({ 11 | describe("a hocon loader and provider test") { 12 | val loader = HoconConfigLoader("hocon.conf") 13 | val provider = Providers.proxy(loader) 14 | val bind = provider.bind("ktor") 15 | bind.deployment().port.should.be.equal(8080) 16 | bind.deployment().host.should.be.`null` 17 | bind.deployment().ssl.port.should.be.`null` 18 | bind.deployment().ssl.keyStore.should.be.equal("goodKeyStore") 19 | bind.deployment().watch.should.be.equal(listOf("hello", "bye")) 20 | bind.deployment().random.should.be.`null` 21 | bind.users.size.should.be.equal(2) 22 | bind.users[0].age().should.be.equal(1) 23 | bind.users[1].age().should.be.equal(100) 24 | bind.users[0].name.should.be.equal("pepe") 25 | bind.users[1].name.should.be.equal("thefrog") 26 | bind.application.modules.size.should.be.equal(1) 27 | bind.application.modules[0].should.be.equal(KtorConfig::class.java) 28 | } 29 | 30 | describe("a short syntax hocon file") { 31 | val loader = HoconConfigLoader("hocon2.conf") 32 | val provider = Providers.proxy(loader) 33 | val deployment = provider.bind("ktor.deployment") 34 | 35 | deployment.port.should.be.equal(8080) 36 | } 37 | }) 38 | 39 | interface KtorConfig { 40 | fun deployment(): KtorDeploymentConfig 41 | val users: List 42 | val application: KtorModules 43 | val commands: Map 44 | } 45 | 46 | interface CommandConfiguration { 47 | val isgoodcommand: Boolean 48 | } 49 | 50 | interface KtorModules { 51 | val modules: List> 52 | } 53 | 54 | interface KtorDeploymentConfig { 55 | val host: String? 56 | val random: String? 57 | val port: Int get() = 80 58 | val watch: List 59 | val ssl: KtorDeploymentSslConfig 60 | } 61 | 62 | interface User { 63 | val name: String 64 | fun age(): Int 65 | } 66 | 67 | interface KtorDeploymentSslConfig { 68 | val port: Int? 69 | val keyStore: String 70 | } -------------------------------------------------------------------------------- /cfg4k-hocon/src/test/kotlin/com/jdiazcano/hocon/ProviderWithMaps.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.hocon 2 | 3 | import com.jdiazcano.cfg4k.hocon.HoconConfigLoader 4 | import com.jdiazcano.cfg4k.providers.Providers 5 | import com.jdiazcano.cfg4k.providers.bind 6 | import com.winterbe.expekt.should 7 | import org.jetbrains.spek.api.Spek 8 | import org.jetbrains.spek.api.dsl.describe 9 | 10 | class ProviderWithMaps : Spek({ 11 | describe("a hocon loader and provider test") { 12 | val loader = HoconConfigLoader("hocon.conf") 13 | val provider = Providers.proxy(loader) 14 | val bind = provider.bind("ktor") 15 | 16 | val commands = bind.commands 17 | 18 | commands["test"]!!.isgoodcommand.should.be.`true` 19 | commands["secondtest"]!!.isgoodcommand.should.be.`true` 20 | commands["thirdtest"]!!.isgoodcommand.should.be.`false` 21 | } 22 | }) -------------------------------------------------------------------------------- /cfg4k-hocon/src/test/resources/hocon.conf: -------------------------------------------------------------------------------- 1 | ktor { 2 | deployment { 3 | port = 8080 4 | random = null 5 | watch = [ "hello", "bye" ] 6 | ssl { 7 | keyStore = goodKeyStore 8 | } 9 | } 10 | 11 | users: [ 12 | { 13 | "name": "pepe", 14 | "age": 1 15 | }, 16 | { 17 | "name": "thefrog", 18 | "age": 100 19 | } 20 | ] 21 | 22 | application { 23 | modules = [ com.jdiazcano.hocon.KtorConfig ] 24 | } 25 | 26 | deploymentPort: ${ktor.deployment.port} 27 | 28 | commands { 29 | test { 30 | isgoodcommand = true 31 | } 32 | secondtest { 33 | isgoodcommand = true 34 | } 35 | thirdtest { 36 | isgoodcommand = false 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /cfg4k-hocon/src/test/resources/hocon2.conf: -------------------------------------------------------------------------------- 1 | ktor.deployment.port = 8080 -------------------------------------------------------------------------------- /cfg4k-hocon/src/test/resources/test.properties: -------------------------------------------------------------------------------- 1 | a=b 2 | c=d 3 | integerProperty=1 4 | longProperty=2 5 | shortProperty=1 6 | doubleProperty=1.1 7 | floatProperty=2.1 8 | byteProperty=2 9 | booleanProperty=true 10 | enumList=A,B 11 | toString=this should not be ever used -------------------------------------------------------------------------------- /cfg4k-json/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: '../gradle/versions.gradle' 2 | 3 | dependencies { 4 | compile project(":cfg4k-core") 5 | compile libraries.jetbrains.kotlin.stdlib 6 | compile libraries.klaxon 7 | 8 | testCompile libraries.jetbrains.spek.api 9 | testCompile libraries.junitrunner 10 | testCompile libraries.expekt 11 | testRuntime libraries.jetbrains.spek.engine 12 | } -------------------------------------------------------------------------------- /cfg4k-json/src/main/kotlin/com/jdiazcano/cfg4k/json/JsonConfigLoader.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 Javier Díaz-Cano Martín-Albo (javierdiazcanom@gmail.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.jdiazcano.cfg4k.json 17 | 18 | import com.beust.klaxon.Parser 19 | import com.jdiazcano.cfg4k.loaders.DefaultConfigLoader 20 | import com.jdiazcano.cfg4k.sources.ConfigSource 21 | 22 | open class JsonConfigLoader( 23 | private val configSource: ConfigSource 24 | ) : DefaultConfigLoader() { 25 | 26 | protected val parser = Parser.default() 27 | 28 | init { 29 | loadProperties() 30 | } 31 | 32 | override fun reload() { 33 | loadProperties() 34 | } 35 | 36 | protected fun loadProperties() { 37 | configSource.read().use { 38 | root = parser.asConfigObjectFromJson(it) 39 | } 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /cfg4k-json/src/main/kotlin/com/jdiazcano/cfg4k/json/JsonToConfigObject.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.json 2 | 3 | import com.beust.klaxon.JsonArray 4 | import com.beust.klaxon.JsonObject 5 | import com.beust.klaxon.Parser 6 | import com.jdiazcano.cfg4k.core.ConfigObject 7 | import com.jdiazcano.cfg4k.core.ListConfigObject 8 | import com.jdiazcano.cfg4k.core.MapConfigObject 9 | import com.jdiazcano.cfg4k.core.StringConfigObject 10 | import java.io.InputStream 11 | 12 | /** 13 | * Parse a json string as a ConfigObject 14 | */ 15 | fun Parser.asConfigObjectFromJson(input: InputStream): ConfigObject { 16 | val parsed = parse(input) 17 | return when (parsed) { 18 | is JsonObject -> parseObject(parsed) 19 | is JsonArray<*> -> parseArray(parsed) 20 | else -> throw UnsupportedOperationException("Input must be array or object.") 21 | } 22 | } 23 | 24 | private fun parseObject(parsed: JsonObject): ConfigObject { 25 | return MapConfigObject(parsed.map { (key, value) -> 26 | when (value) { 27 | is JsonArray<*> -> key to parseArray(value) 28 | is JsonObject -> key to parseObject(value) 29 | else -> key to StringConfigObject(value.toString()) 30 | } 31 | }.toMap(hashMapOf())) 32 | } 33 | 34 | private fun parseArray(array: JsonArray<*>): ConfigObject { 35 | return ListConfigObject(array.map { item -> 36 | when (item) { 37 | is JsonObject -> parseObject(item) 38 | is JsonArray<*> -> parseArray(item) 39 | else -> StringConfigObject(item.toString()) 40 | } 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /cfg4k-json/src/test/kotlin/com/jdiazcano/cfg4k/json/Enumerito.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.json 2 | 3 | import java.io.File 4 | import java.math.BigDecimal 5 | import java.math.BigInteger 6 | import java.net.URI 7 | import java.net.URL 8 | import java.nio.file.Path 9 | 10 | interface TestBinder { 11 | fun integerProperty(): Int 12 | fun a(): String 13 | fun c(): String 14 | fun booleanProperty(): Boolean 15 | fun longProperty(): Long 16 | fun shortProperty(): Short 17 | fun doubleProperty(): Double 18 | fun floatProperty(): Float 19 | fun byteProperty(): Byte 20 | fun list(): List 21 | fun floatList(): List 22 | fun complexSet(): Set 23 | fun bigIntegerProperty(): BigInteger 24 | fun bigDecimalProperty(): BigDecimal 25 | fun uri(): URI 26 | fun url(): URL 27 | fun file(): File 28 | fun path(): Path 29 | val listOfLists: List> 30 | val myCoolMap: Map 31 | val myCoolComplexMap: Map>> 32 | } 33 | 34 | enum class TestEnum { 35 | TEST, TEST1, TEST2 36 | } 37 | 38 | interface BindedEnum { 39 | fun thisWillBeEnum(): TestEnum 40 | } 41 | 42 | interface PrefixedBindedEnum { 43 | fun enumtest(): TestEnum 44 | } 45 | 46 | interface Binded { 47 | fun list(): List 48 | fun set(): Set 49 | fun enumerito(): List 50 | } 51 | 52 | enum class Enumerito { 53 | A, B, C 54 | } 55 | 56 | interface Doge { 57 | fun wow(): String 58 | fun doge(): Int 59 | } 60 | 61 | interface NestedBinder { 62 | fun nested(): TestBinder 63 | fun normal(): Int 64 | } 65 | 66 | interface SuperNested { 67 | fun supernested(): NestedBinder 68 | fun normal(): Int 69 | } -------------------------------------------------------------------------------- /cfg4k-json/src/test/kotlin/com/jdiazcano/cfg4k/json/JsonConfigLoaderTest.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.json 2 | 3 | import com.jdiazcano.cfg4k.core.toConfig 4 | import com.jdiazcano.cfg4k.providers.ProxyConfigProvider 5 | import com.jdiazcano.cfg4k.providers.bind 6 | import com.jdiazcano.cfg4k.providers.get 7 | import com.jdiazcano.cfg4k.sources.URLConfigSource 8 | import com.jdiazcano.cfg4k.utils.typeOf 9 | import com.winterbe.expekt.should 10 | import org.jetbrains.spek.api.Spek 11 | import org.jetbrains.spek.api.dsl.describe 12 | import org.jetbrains.spek.api.dsl.it 13 | 14 | class JsonConfigLoaderTest : Spek({ 15 | val loader = JsonConfigLoader(URLConfigSource(javaClass.classLoader.getResource("test.json"))) 16 | val provider = ProxyConfigProvider(loader) 17 | describe("a json config loader") { 18 | it("a value should be b") { 19 | loader.get("a").should.be.equal("b".toConfig()) 20 | loader.get("nested.a").should.be.equal("nestedb".toConfig()) 21 | } 22 | } 23 | 24 | it("Simple property test") { 25 | val testBinder: List = provider.get("list", typeOf>()) 26 | testBinder.should.be.equal(listOf(1, 2, 3, 4, 5, 6, 7)) 27 | val listOfIntegers: List? = provider.getOrNull("list", typeOf?>()) 28 | listOfIntegers.should.not.be.`null` 29 | listOfIntegers.should.be.equal(listOf(1, 2, 3, 4, 5, 6, 7)) 30 | val betterIntList: List = provider.get("betterIntList", typeOf>()) 31 | betterIntList.should.be.equal(listOf(1, 2, 100)) 32 | val betterStringList: List = provider.get("betterStringList", typeOf>()) 33 | betterStringList.should.be.equal(listOf("a", "b", "c")) 34 | val betterEnumList = provider.get>("betterEnumList") 35 | betterEnumList.should.be.equal(listOf(Enumerito.A, Enumerito.B)) 36 | val x = provider.bind("").complexList 37 | x.size.should.be.equal(2) 38 | x[0].wow().should.be.equal("such0") 39 | x[1].wow().should.be.equal("such1") 40 | x[0].doge().should.be.equal(0) 41 | x[1].doge().should.be.equal(1) 42 | } 43 | }) 44 | 45 | interface Keepo { 46 | val complexList: List 47 | } -------------------------------------------------------------------------------- /cfg4k-json/src/test/kotlin/com/jdiazcano/cfg4k/json/JsonConfigReloaderTest.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.json 2 | 3 | import com.jdiazcano.cfg4k.core.toConfig 4 | import com.jdiazcano.cfg4k.sources.FileConfigSource 5 | import com.winterbe.expekt.should 6 | import org.jetbrains.spek.api.Spek 7 | import org.jetbrains.spek.api.dsl.describe 8 | import org.jetbrains.spek.api.dsl.it 9 | import java.io.File 10 | 11 | class JsonConfigReloaderTest: Spek({ 12 | describe("a reloadable properties config loader") { 13 | val file = File("reloadedfile.properties") 14 | file.createNewFile() 15 | file.writeText(""" 16 | { 17 | "a": "b", 18 | "c": "d" 19 | } 20 | """.trim()) 21 | 22 | val loader = JsonConfigLoader(FileConfigSource(file)) 23 | it("values should be equals like in the string") { 24 | loader.get("a").should.be.equal("b".toConfig()) 25 | loader.get("c").should.be.equal("d".toConfig()) 26 | } 27 | 28 | it("now we should have a reloaded values") { 29 | file.delete() 30 | file.createNewFile() 31 | file.writeText(""" 32 | { 33 | "a": "reloadedb", 34 | "c": "reloadedd" 35 | } 36 | """.trim()) 37 | 38 | loader.reload() 39 | loader.get("a").should.be.equal("reloadedb".toConfig()) 40 | loader.get("c").should.be.equal("reloadedd".toConfig()) 41 | 42 | file.delete() 43 | } 44 | } 45 | }) -------------------------------------------------------------------------------- /cfg4k-json/src/test/kotlin/com/jdiazcano/cfg4k/json/JsonToConfigObjectMapperTest.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.json 2 | 3 | import com.beust.klaxon.Parser 4 | import com.jdiazcano.cfg4k.core.MapConfigObject 5 | import com.jdiazcano.cfg4k.core.toConfig 6 | import com.winterbe.expekt.should 7 | import org.jetbrains.spek.api.Spek 8 | import org.jetbrains.spek.api.dsl.describe 9 | import java.io.ByteArrayInputStream 10 | 11 | class JsonToConfigObjectMapperTest : Spek({ 12 | val parser = Parser() 13 | val json = 14 | """{ 15 | "int": 1, 16 | "str": "string", 17 | "obj": { 18 | "objstr": "objstring", 19 | "objint": 11 20 | }, 21 | "listobj": [ 22 | { 23 | "liststr": "objstring0", 24 | "listint": 0, 25 | "objinlist": { 26 | "test": 0 27 | } 28 | }, 29 | { 30 | "liststr": "objstring1", 31 | "listint": 1, 32 | "objinlist": { 33 | "test": 1 34 | } 35 | } 36 | ], 37 | "listitems": [ 0, 1, 2, 3, 4 ] 38 | }""" 39 | describe("a parser that can map to config object") { 40 | val configObject = parser.asConfigObjectFromJson(ByteArrayInputStream(json.toByteArray())) 41 | 42 | configObject.isObject().should.be.`true` 43 | configObject.asObject()["int"].should.be.equal(1.toConfig()) 44 | configObject.asObject()["str"].should.be.equal("string".toConfig()) 45 | configObject.asObject()["obj"].should.be.equal(MapConfigObject( 46 | mapOf("objstr" to "objstring".toConfig(), 47 | "objint" to "11".toConfig()) 48 | )) 49 | val listConfigObject = configObject.asObject()["listobj"] 50 | listConfigObject.should.not.be.`null` 51 | listConfigObject?.let { 52 | it.isList().should.be.`true` 53 | it.asList().size.should.be.equal(2) 54 | it.asList().forEachIndexed { index, obj -> 55 | obj.isObject().should.be.`true` 56 | obj.asObject()["liststr"].should.be.equal("objstring$index".toConfig()) 57 | obj.asObject()["listint"].should.be.equal(index.toConfig()) 58 | obj.asObject()["objinlist"]!!.isObject().should.be.`true` 59 | obj.asObject()["objinlist"]!!.asObject()["test"].should.be.equal(index.toConfig()) 60 | } 61 | } 62 | val listItemsObject = configObject.asObject()["listitems"] 63 | listItemsObject!!.isList().should.be.`true` 64 | listItemsObject.asList().size.should.be.equal(5) 65 | listItemsObject.asList().forEachIndexed { index, configObject -> configObject.should.be.equal(index.toConfig()) } 66 | } 67 | }) -------------------------------------------------------------------------------- /cfg4k-json/src/test/resources/reloadedtest.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": "reloadedb", 3 | "c": "reloadedd", 4 | "integerProperty": 1, 5 | "longProperty": 2, 6 | "shortProperty": 1, 7 | "doubleProperty": 1.1, 8 | "floatProperty": 2.1, 9 | "byteProperty": 2, 10 | "list": "1,2,3", 11 | "booleanProperty": true, 12 | "nested": { 13 | "a": "reloaded nestedb" 14 | } 15 | } -------------------------------------------------------------------------------- /cfg4k-json/src/test/resources/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": "b", 3 | "c": "d", 4 | "integerProperty": 1, 5 | "longProperty": 2, 6 | "shortProperty": 1, 7 | "doubleProperty": 1.1, 8 | "floatProperty": 2.1, 9 | "byteProperty": 2, 10 | "list": [ 11 | 1, 12 | 2, 13 | 3, 14 | 4, 15 | 5, 16 | 6, 17 | 7 18 | ], 19 | "myCoolMap": { 20 | "one": 1, 21 | "two": 2 22 | }, 23 | "myCoolComplexMap": { 24 | "one": { 25 | "1": [ 26 | "one", 27 | "uno" 28 | ], 29 | "10": [ 30 | "diez", 31 | "ten" 32 | ] 33 | }, 34 | "two": { 35 | "2": [ 36 | "dos", 37 | "two" 38 | ], 39 | "20": [ 40 | "veinte", 41 | "twenty" 42 | ] 43 | } 44 | }, 45 | "floatList": [ 46 | 1.2, 47 | 2.2, 48 | 3.2 49 | ], 50 | "complexList": [ 51 | { 52 | "wow": "such0", 53 | "doge": 0 54 | }, 55 | { 56 | "wow": "such1", 57 | "doge": 1 58 | } 59 | ], 60 | "complexSet": [ 61 | { 62 | "wow": "such0", 63 | "doge": 0 64 | }, 65 | { 66 | "wow": "such1", 67 | "doge": 1 68 | } 69 | ], 70 | "booleanProperty": true, 71 | "bigIntegerProperty": 1, 72 | "bigDecimalProperty": 1.1, 73 | "dateProperty": "01-01-2017", 74 | "calendarProperty": "01-01-2017", 75 | "localDateProperty": "01-01-2017", 76 | "isoLocalDateProperty": "2017-01-01", 77 | "localDateTimeProperty": "01-01-2017 18:01:31", 78 | "isoLocalDateTimeProperty": "2017-01-01T18:01:31", 79 | "zonedDateTimeProperty": "01-01-2017 18:01:31", 80 | "isoZonedDateTimeProperty": "2017-01-01T18:01:31+01:00", 81 | "offsetDateTimeProperty": "01-01-2017 18:01:31+01:00", 82 | "isoOffsetDateTimeProperty": "2017-01-01T18:01:31+01:00", 83 | "offsetTimeProperty": "18:01:31+01:00", 84 | "isoOffsetTimeProperty": "18:01:31+01:00", 85 | "nested": { 86 | "a": "nestedb" 87 | }, 88 | "betterIntList": [ 89 | 1, 90 | 2, 91 | 100 92 | ], 93 | "betterStringList": [ 94 | "a", 95 | "b", 96 | "c" 97 | ], 98 | "betterEnumList": [ 99 | "A", 100 | "B" 101 | ], 102 | "url": "https://www.amazon.com", 103 | "uri": "https://www.amazon.com", 104 | "path": "mypath.txt", 105 | "file": "myfile.txt", 106 | "toString": "this should not be ever used", 107 | "listOfLists": [ 108 | [ 109 | 1, 110 | 2 111 | ], 112 | [ 113 | 3, 114 | 4 115 | ] 116 | ] 117 | } -------------------------------------------------------------------------------- /cfg4k-s3/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: '../gradle/versions.gradle' 2 | 3 | dependencies { 4 | compile project(":cfg4k-core") 5 | compile libraries.jetbrains.kotlin.stdlib 6 | compile libraries.aws.s3 7 | 8 | testCompile project(":cfg4k-json") 9 | testCompile libraries.jetbrains.spek.api 10 | testCompile libraries.junitrunner 11 | testCompile libraries.expekt 12 | testRuntime libraries.jetbrains.spek.engine 13 | } -------------------------------------------------------------------------------- /cfg4k-s3/src/main/kotlin/com/jdiazcano/cfg4k/s3/S3ConfigSource.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.s3 2 | 3 | import com.amazonaws.services.s3.AmazonS3 4 | import com.jdiazcano.cfg4k.sources.ConfigSource 5 | import java.io.InputStream 6 | 7 | /** 8 | * Given an S3 Client, bucket and objectName it will convert it to a ConfigSource. 9 | * 10 | * @since 0.9.0 11 | */ 12 | class S3ConfigSource( 13 | private val client: AmazonS3, 14 | private val bucket: String, 15 | private val objectName: String 16 | ) : ConfigSource { 17 | 18 | override fun read(): InputStream { 19 | return client.getObject(bucket, objectName).objectContent 20 | } 21 | 22 | override fun toString(): String { 23 | return "S3ConfigSource(client=$client, bucket='$bucket', objectName='$objectName')" 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /cfg4k-s3/src/test/kotlin/com/jdiazcano/cfg4k/s3/S3ConfigSourceTest.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.s3 2 | 3 | import com.amazonaws.regions.Regions 4 | import com.amazonaws.services.s3.AmazonS3ClientBuilder 5 | import com.jdiazcano.cfg4k.json.JsonConfigLoader 6 | import com.jdiazcano.cfg4k.providers.DefaultConfigProvider 7 | import com.jdiazcano.cfg4k.providers.cache 8 | import com.jdiazcano.cfg4k.providers.get 9 | import com.winterbe.expekt.should 10 | import org.jetbrains.spek.api.Spek 11 | import org.jetbrains.spek.api.dsl.describe 12 | 13 | class S3ConfigSourceTest : Spek({ 14 | describe("a config source that can fetch data") { 15 | val client = AmazonS3ClientBuilder.standard().withRegion(Regions.EU_WEST_1).build() 16 | val source = S3ConfigSource(client, "mtln-public-data", "Samples/airports.json") 17 | val loader = JsonConfigLoader(source) 18 | val provider = DefaultConfigProvider(loader).cache() 19 | val airports = provider.get>() 20 | airports.should.not.be.empty 21 | } 22 | }) 23 | 24 | private interface Airport { 25 | val iata: String 26 | val airport: String 27 | val city: String 28 | val state: String 29 | val country: String 30 | val lat: Double 31 | val long: Double 32 | } -------------------------------------------------------------------------------- /cfg4k-schema.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdiazcano/cfg4k/d2fa6a616a4860445d7268a7f932e41ad0c45993/cfg4k-schema.png -------------------------------------------------------------------------------- /cfg4k-yaml/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: '../gradle/versions.gradle' 2 | 3 | dependencies { 4 | compile project(":cfg4k-core") 5 | compile libraries.jetbrains.kotlin.stdlib 6 | compile libraries.snakeyaml 7 | 8 | testCompile libraries.jetbrains.spek.api 9 | testCompile libraries.junitrunner 10 | testCompile libraries.expekt 11 | testRuntime libraries.jetbrains.spek.engine 12 | } -------------------------------------------------------------------------------- /cfg4k-yaml/src/main/kotlin/com/jdiazcano/cfg4k/yaml/YamlConfigLoader.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.yaml 2 | 3 | import com.jdiazcano.cfg4k.core.toConfig 4 | import com.jdiazcano.cfg4k.loaders.DefaultConfigLoader 5 | import com.jdiazcano.cfg4k.sources.ConfigSource 6 | import org.yaml.snakeyaml.Yaml 7 | 8 | class YamlConfigLoader(private val configSource: ConfigSource) : DefaultConfigLoader() { 9 | 10 | init { 11 | loadProperties() 12 | } 13 | 14 | private fun loadProperties() { 15 | configSource.read().use { 16 | val load = Yaml().load(it) as Map 17 | root = load.toConfig() 18 | } 19 | } 20 | 21 | override fun reload() { 22 | loadProperties() 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /cfg4k-yaml/src/test/kotlin/com/jdiazcano/cfg4k/yaml/YamlConfigLoaderTest.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.yaml 2 | 3 | import com.jdiazcano.cfg4k.core.toConfig 4 | import com.jdiazcano.cfg4k.providers.Providers.proxy 5 | import com.jdiazcano.cfg4k.providers.bind 6 | import com.jdiazcano.cfg4k.sources.URLConfigSource 7 | import com.winterbe.expekt.should 8 | import org.jetbrains.spek.api.Spek 9 | import org.jetbrains.spek.api.dsl.describe 10 | import org.jetbrains.spek.api.dsl.it 11 | 12 | class YamlConfigLoaderTest : Spek({ 13 | 14 | val loaders = listOf( 15 | YamlConfigLoader(URLConfigSource(javaClass.classLoader.getResource("test.yml"))) 16 | ) 17 | 18 | loaders.forEachIndexed { i, loader -> 19 | describe("loader[$i]") { 20 | it("loader properties") { 21 | loader.get("integerProperty").should.be.equal(1.toConfig()) 22 | loader.get("longProperty").should.be.equal(2.toConfig()) 23 | loader.get("shortProperty").should.be.equal(1.toConfig()) 24 | loader.get("floatProperty").should.be.equal(2.1.toConfig()) 25 | loader.get("doubleProperty").should.be.equal(1.1.toConfig()) 26 | loader.get("byteProperty").should.be.equal("2".toConfig()) 27 | loader.get("booleanProperty").should.be.equal("true".toConfig()) 28 | loader.get("nested.nesteda").should.be.equal("nestedb".toConfig()) 29 | } 30 | 31 | it("works with binding") { 32 | val provider = proxy(loader) 33 | 34 | val testBinder = provider.bind("") 35 | testBinder.booleanProperty().should.be.`true` 36 | testBinder.integerProperty().should.be.equal(1) 37 | testBinder.longProperty().should.be.equal(2) 38 | testBinder.shortProperty().should.be.equal(1) 39 | testBinder.floatProperty().should.be.equal(2.1F) 40 | testBinder.doubleProperty().should.be.equal(1.1) 41 | testBinder.byteProperty().should.be.equal(2) 42 | testBinder.a().should.be.equal("b") 43 | testBinder.c().should.be.equal("d") 44 | testBinder.list().should.be.equal(listOf(1, 2, 3)) 45 | testBinder.floatList().should.be.equal(listOf(1.2F, 2.2F, 3.2F)) 46 | 47 | testBinder.complexList.size.should.be.equal(2) 48 | testBinder.complexList[0].age().should.be.equal(1) 49 | testBinder.complexList[1].age().should.be.equal(100) 50 | testBinder.complexList[0].name.should.be.equal("pepe") 51 | testBinder.complexList[1].name.should.be.equal("thefrog") 52 | 53 | testBinder.complexSet.size.should.be.equal(2) 54 | } 55 | } 56 | } 57 | 58 | }) 59 | 60 | interface TestBinder { 61 | fun integerProperty(): Int 62 | fun a(): String 63 | fun c(): String 64 | fun booleanProperty(): Boolean 65 | fun longProperty(): Long 66 | fun shortProperty(): Short 67 | fun doubleProperty(): Double 68 | fun floatProperty(): Float 69 | fun byteProperty(): Byte 70 | fun list(): List 71 | fun floatList(): List 72 | 73 | val complexList: List 74 | val complexSet: List 75 | } 76 | 77 | interface User { 78 | val name: String 79 | fun age(): Int 80 | } 81 | -------------------------------------------------------------------------------- /cfg4k-yaml/src/test/kotlin/com/jdiazcano/cfg4k/yaml/YamlConfigMapperTest.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.yaml 2 | 3 | import com.jdiazcano.cfg4k.core.toConfig 4 | import com.winterbe.expekt.should 5 | import org.jetbrains.spek.api.Spek 6 | import org.jetbrains.spek.api.dsl.describe 7 | import org.jetbrains.spek.api.dsl.it 8 | import org.yaml.snakeyaml.Yaml 9 | 10 | class YamlConfigMapperTest : Spek({ 11 | describe("should work too") { 12 | it("should work") { 13 | val config = javaClass.classLoader.getResource("test.yml").openStream().use { 14 | Yaml().load(it) as Map 15 | }.toConfig() 16 | 17 | config.asObject()["integerProperty"]!!.should.be.equal(1.toConfig()) 18 | config.asObject()["fruits"]!!.should.be.equal(listOf("apple", "orange").toConfig()) 19 | config.asObject()["nested"]!!.should.be.equal(mapOf("nesteda" to "nestedb", "nestedc" to "nestedd").toConfig()) 20 | } 21 | } 22 | }) -------------------------------------------------------------------------------- /cfg4k-yaml/src/test/kotlin/com/jdiazcano/cfg4k/yaml/YamlConfigReloadedTest.kt: -------------------------------------------------------------------------------- 1 | package com.jdiazcano.cfg4k.yaml 2 | 3 | import com.jdiazcano.cfg4k.core.toConfig 4 | import com.jdiazcano.cfg4k.sources.FileConfigSource 5 | import com.winterbe.expekt.should 6 | import org.jetbrains.spek.api.Spek 7 | import org.jetbrains.spek.api.dsl.describe 8 | import org.jetbrains.spek.api.dsl.it 9 | import java.io.File 10 | 11 | class YamlConfigReloaderTest: Spek({ 12 | describe("a reloadable properties config loader") { 13 | val file = File("reloadedfile.properties") 14 | file.createNewFile() 15 | file.writeText(""" 16 | a: b 17 | c: d 18 | """.trim()) 19 | 20 | val loader = YamlConfigLoader(FileConfigSource(file)) 21 | it("values should be equals like in the string") { 22 | loader.get("a").should.be.equal("b".toConfig()) 23 | loader.get("c").should.be.equal("d".toConfig()) 24 | } 25 | 26 | it("now we should have a reloaded values") { 27 | file.delete() 28 | file.createNewFile() 29 | file.writeText(""" 30 | a: reloadedb 31 | c: reloadedd 32 | """.trim()) 33 | 34 | loader.reload() 35 | loader.get("a").should.be.equal("reloadedb".toConfig()) 36 | loader.get("c").should.be.equal("reloadedd".toConfig()) 37 | 38 | file.delete() 39 | } 40 | } 41 | }) -------------------------------------------------------------------------------- /cfg4k-yaml/src/test/resources/test.yml: -------------------------------------------------------------------------------- 1 | a: b 2 | c: d 3 | integerProperty: 1 4 | longProperty: 2 5 | shortProperty: 1 6 | doubleProperty: 1.1 7 | floatProperty: 2.1 8 | byteProperty: 2 9 | list: 10 | - 1 11 | - 2 12 | - 3 13 | floatList: 14 | - 1.2 15 | - 2.2 16 | - 3.2 17 | booleanProperty: true 18 | enumList: A,B 19 | toString: this should not be ever used 20 | fruits: 21 | - apple 22 | - orange 23 | complexList: 24 | - name: pepe 25 | age: 1 26 | - name: thefrog 27 | age: 100 28 | complexSet: 29 | - firstKey: firstValue0 30 | secondKey: secondValue0 31 | - firstKey: firstValue1 32 | secondKey: secondValue1 33 | nested: 34 | nesteda: nestedb 35 | nestedc: nestedd -------------------------------------------------------------------------------- /encrypted_cfg4k: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdiazcano/cfg4k/d2fa6a616a4860445d7268a7f932e41ad0c45993/encrypted_cfg4k -------------------------------------------------------------------------------- /examples/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | 3 | repositories { 4 | mavenLocal() 5 | mavenCentral() 6 | jcenter() 7 | } 8 | 9 | dependencies { 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.21" 11 | } 12 | 13 | } 14 | 15 | apply plugin: 'kotlin' 16 | 17 | repositories { 18 | mavenCentral() 19 | jcenter() 20 | } 21 | 22 | dependencies { 23 | compile 'org.jetbrains.kotlin:kotlin-stdlib:1.3.21' 24 | compile 'com.jdiazcano.cfg4k:cfg4k-bytebuddy:0.9.2' 25 | compile 'com.jdiazcano.cfg4k:cfg4k-core:0.9.2' 26 | compile 'com.jdiazcano.cfg4k:cfg4k-git:0.9.2' 27 | compile 'com.jdiazcano.cfg4k:cfg4k-hocon:0.9.2' 28 | compile 'com.jdiazcano.cfg4k:cfg4k-json:0.9.2' 29 | compile 'com.jdiazcano.cfg4k:cfg4k-yaml:0.9.2' 30 | } -------------------------------------------------------------------------------- /examples/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdiazcano/cfg4k/d2fa6a616a4860445d7268a7f932e41ad0c45993/examples/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /examples/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Feb 27 20:51:09 GMT 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.0.1-bin.zip 7 | -------------------------------------------------------------------------------- /examples/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /examples/src/main/kotlin/BytebuddyExample.kt: -------------------------------------------------------------------------------- 1 | 2 | import com.jdiazcano.cfg4k.bytebuddy.ByteBuddyBinder 3 | import com.jdiazcano.cfg4k.json.JsonConfigLoader 4 | import com.jdiazcano.cfg4k.providers.DefaultConfigProvider 5 | import com.jdiazcano.cfg4k.providers.bind 6 | import com.jdiazcano.cfg4k.providers.get 7 | import com.jdiazcano.cfg4k.sources.StringConfigSource 8 | import utils.School 9 | 10 | fun main(args: Array) { 11 | val source = StringConfigSource(schoolJson) 12 | val loader = JsonConfigLoader(source) 13 | val provider = DefaultConfigProvider(loader, binder = ByteBuddyBinder()) 14 | 15 | val secondProfessorName = provider.get("professors[1].name") 16 | println("SecondProfessor name: $secondProfessorName") 17 | 18 | val school = provider.bind() // Here we will have a compiled class instead of a Proxy 19 | 20 | println(school) // We don't get an InvocationHandler 21 | 22 | println("Name: ${school.professors.first().name}") 23 | println("Age: ${school.professors.first().age}") 24 | } -------------------------------------------------------------------------------- /examples/src/main/kotlin/CustomParser.kt: -------------------------------------------------------------------------------- 1 | 2 | import com.jdiazcano.cfg4k.core.ConfigContext 3 | import com.jdiazcano.cfg4k.core.ConfigObject 4 | import com.jdiazcano.cfg4k.json.JsonConfigLoader 5 | import com.jdiazcano.cfg4k.parsers.Parser 6 | import com.jdiazcano.cfg4k.parsers.Parsers 7 | import com.jdiazcano.cfg4k.providers.DefaultConfigProvider 8 | import com.jdiazcano.cfg4k.providers.get 9 | import com.jdiazcano.cfg4k.sources.StringConfigSource 10 | import com.jdiazcano.cfg4k.utils.TypeStructure 11 | 12 | class Point(val x: Int, val y: Int) { 13 | override fun toString(): String { 14 | return "Point(x=$x, y=$y)" 15 | } 16 | } 17 | 18 | object PointParser: Parser { 19 | override fun parse(context: ConfigContext, value: ConfigObject, typeStructure: TypeStructure) = Point( 20 | value.asString().split(',')[0].toInt(), 21 | value.asString().split(',')[1].toInt() 22 | ) 23 | 24 | } 25 | 26 | const val listOfPoints = """[ 27 | "1,2", 28 | "2,2", 29 | "3,2" 30 | ]""" 31 | 32 | /** 33 | * With the class and parser alternative you have a cool way of printing/comparing as 34 | * you can have data classes which will generate the toString and equals/hashcode for you. 35 | */ 36 | fun main(args: Array) { 37 | Parsers.addParser(Point::class.java, PointParser) 38 | 39 | val source = StringConfigSource(listOfPoints) 40 | val loader = JsonConfigLoader(source) 41 | val provider = DefaultConfigProvider(loader) 42 | 43 | provider.get>().forEach { 44 | println(it) 45 | } 46 | } -------------------------------------------------------------------------------- /examples/src/main/kotlin/FirstExample.kt: -------------------------------------------------------------------------------- 1 | 2 | import com.jdiazcano.cfg4k.json.JsonConfigLoader 3 | import com.jdiazcano.cfg4k.providers.DefaultConfigProvider 4 | import com.jdiazcano.cfg4k.providers.bind 5 | import com.jdiazcano.cfg4k.providers.get 6 | import com.jdiazcano.cfg4k.sources.StringConfigSource 7 | import utils.School 8 | 9 | const val schoolJson = """{ 10 | "name": "Nice school", 11 | "professors": [ 12 | { 13 | "name": "John", 14 | "age": 30 15 | }, 16 | { 17 | "name": "Alice", 18 | "age": 30 19 | } 20 | ] 21 | }""" 22 | 23 | fun main(args: Array) { 24 | val source = StringConfigSource(schoolJson) // 1- Define the source 25 | val loader = JsonConfigLoader(source) // 2- Define HOW you want to load it (as Json in this case) 26 | val provider = DefaultConfigProvider(loader) // 3- Create a provider that will let you get/bind 27 | 28 | val secondProfessorName = provider.get("professors[1].name") 29 | println("SecondProfessor name: $secondProfessorName") 30 | 31 | val school = provider.bind() // You can omit the binding if there's nothing else in the document 32 | 33 | println("Name: ${school.professors.first().name}") 34 | println("Age: ${school.professors.first().age}") 35 | } -------------------------------------------------------------------------------- /examples/src/main/kotlin/utils/Beans.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * * Copyright 2016-2018 Javier Díaz-Cano Martín-Albo (javierdiazcanom@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. 3 | */ 4 | 5 | package utils 6 | 7 | interface School { 8 | val name: String 9 | val professors: List 10 | val classes: List 11 | } 12 | 13 | interface Class { 14 | val alumns: List 15 | val floor: Int 16 | } 17 | 18 | interface Person { 19 | val name: String 20 | val age: Int 21 | } -------------------------------------------------------------------------------- /examples/src/main/kotlin/utils/Utils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * * Copyright 2016-2018 Javier Díaz-Cano Martín-Albo (javierdiazcanom@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. 3 | */ 4 | 5 | package utils 6 | 7 | import com.jdiazcano.cfg4k.binders.Binder 8 | import com.jdiazcano.cfg4k.binders.ProxyBinder 9 | import com.jdiazcano.cfg4k.json.JsonConfigLoader 10 | import com.jdiazcano.cfg4k.providers.DefaultConfigProvider 11 | import com.jdiazcano.cfg4k.sources.StringConfigSource 12 | import schoolJson 13 | 14 | fun createSchoolProvider(binder: Binder = ProxyBinder()): DefaultConfigProvider { 15 | val source = StringConfigSource(schoolJson) // 1- Define the source 16 | val loader = JsonConfigLoader(source) // 2- Define HOW you want to load it (as Json in this case) 17 | return DefaultConfigProvider(loader, binder = binder) // 3- Create a provider that will let you get/bind 18 | } 19 | 20 | fun createProvider(string: String): DefaultConfigProvider { 21 | val source = StringConfigSource(string) 22 | val loader = JsonConfigLoader(source) 23 | return DefaultConfigProvider(loader) 24 | } -------------------------------------------------------------------------------- /gradle/versions.gradle: -------------------------------------------------------------------------------- 1 | ext.versions = [ 2 | jetbrains: [ 3 | kotlin: '1.3.70', 4 | spek: '1.1.2', 5 | engine: '1.2.1' 6 | ], 7 | bytebuddy: '1.10.8', 8 | klaxon: '5.2', 9 | typesafe: '1.4.0', 10 | jgit: '4.9.0.201710071750-r', 11 | expekt: '0.5.0', 12 | junitrunner: '1.1.0-M1', 13 | snakeyaml: '1.26', 14 | jcommander: '1.72', 15 | mockwebserver: '4.4.1', 16 | aws: '1.11.745', 17 | logging: '1.7.8', 18 | mockk: '1.9.3', 19 | kotlintest: '3.4.2', 20 | assertk: '0.13' 21 | ] 22 | 23 | ext.libraries = [ 24 | eclipse: [ 25 | jgit: "org.eclipse.jgit:org.eclipse.jgit:$versions.jgit" 26 | ], 27 | jetbrains: [ 28 | kotlin: [ 29 | stdlib: "org.jetbrains.kotlin:kotlin-stdlib:$versions.jetbrains.kotlin", 30 | reflect: "org.jetbrains.kotlin:kotlin-reflect:$versions.jetbrains.kotlin", 31 | test: "org.jetbrains.kotlin:kotlin-test:$versions.jetbrains.kotlin", 32 | testannotations: "org.jetbrains.kotlin:kotlin-test-annotations:$versions.jetbrains.kotlin", 33 | testjunit: "org.jetbrains.kotlin:kotlin-test-junit5:$versions.jetbrains.kotlin" 34 | ], 35 | spek: [ 36 | api: "org.jetbrains.spek:spek-api:$versions.jetbrains.engine", 37 | engine: "org.jetbrains.spek:spek-junit-platform-engine:$versions.jetbrains.engine" 38 | ] 39 | ], 40 | kotlintest: "io.kotlintest:kotlintest-runner-junit5:$versions.kotlintest", 41 | junitrunner: "org.junit.platform:junit-platform-runner:$versions.junitrunner", 42 | expekt: "com.winterbe:expekt:$versions.expekt", 43 | bytebuddy: "net.bytebuddy:byte-buddy:$versions.bytebuddy", 44 | typesafe: "com.typesafe:config:$versions.typesafe", 45 | snakeyaml: "org.yaml:snakeyaml:$versions.snakeyaml", 46 | klaxon: "com.beust:klaxon:$versions.klaxon", 47 | jcommander: "com.beust:jcommander:$versions.jcommander", 48 | mockwebserver: "com.squareup.okhttp3:mockwebserver:$versions.mockwebserver", 49 | aws: [ 50 | s3: "com.amazonaws:aws-java-sdk-s3:$versions.aws" 51 | ], 52 | logging: "io.github.microutils:kotlin-logging:$versions.logging", 53 | mockk: "io.mockk:mockk:$versions.mockk", 54 | assertk: "com.willowtreeapps.assertk:assertk-jvm:$versions.assertk" 55 | ] 56 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdiazcano/cfg4k/d2fa6a616a4860445d7268a7f932e41ad0c45993/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Jul 31 22:59:33 BST 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip 7 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'cfg4k' 2 | include 'cfg4k-core' 3 | include 'cfg4k-json' 4 | include 'cfg4k-bytebuddy' 5 | include 'cfg4k-hocon' 6 | include 'cfg4k-yaml' 7 | include 'cfg4k-git' 8 | include 'cfg4k-cli' 9 | include 'cfg4k-s3' 10 | --------------------------------------------------------------------------------