Apple опубликовал Pkl, язык программирования для определения конфигурации

Компания Apple открыла реализацию языка программирования Pkl, предназначенного для определения конфигурации и продвигающего модель «конфигурация как код». Связанный с Pkl инструментарий написан на Kotlin и опубликован под лицензией Apache. Плагины для работы с кодом на языке Pkl подготовлены для сред разработки IntelliJ, Visual Studio Code и Neovim. В ближайшее время ожидается публикация обработчика LSP (Language Server Protocol).

Pkl сочетает свойства простого для восприятия декларативного языка c расширенными возможностями, свойственными для языков общего назначения. В языке поддерживаются аннотации типов, классы, функции, вычислительные выражения, условия и циклы. Pkl может применяться как для генерации статических конфигураций в разных форматах, таких как JSON, YAML и XML, так для формирования модулей для обработки заданных форматов конфигурации в приложениях на различных языках программирования.

Привязки для интеграции Pkl в приложения подготовлены для Java, Kotlin, Go и Swift. Отдельно предложен модуль для интеграции с фреймворком Spring. Привязки позволяют на основе конфигурации на языке Pkl сформировать готовые пакеты и модули с классами для работы с описанной конфигурацией в приложении. Пакеты с описанием конфигурации могут публиковаться в репозиториях и импортироваться в качестве зависимости, что допускает совместное использование кода Pkl между разными проектами.

Язык предоставляет гибкие средства для определения условий и проверок корректности значений, позволяющих выявлять ошибки в итоговой конфигурации на этапе до её применения в приложении. Например, можно определить допустимый диапазон значений («port: Int(this > 1000)» или «age: Int(isBetween(0, 130))»), обязательность или необязательность заполнения и формат («zipCode: String(matches(Regex(«\d{5}»)))»). В случае присвоения значения, не соответствующего условию, валидатор выведет ошибку (например, при попытке присвоить значение 1001 параметру, определённому как «Int(this > 1000)»).

Например, на языке Pkl можно написать модуль Application.pkl с шаблоном конфигурации подключения к СУБД:


   module Application

   hostname: String
   port: UInt16
   environment: Environment
   database: Database

   class Database {
     username: String
     password: String
     host: String
     port: UInt16
     dbName: String
   }
   typealias Environment = "dev"|"qa"|"prod"

Далее можно создать непосредственно файл конфигурации, использующий данный модуль для проверки корректности значений:


   amends "Application.pkl"
   hostname = "localhost"
   port = 3599
   environment = "dev"
   database {
     host = "localhost"
     port = 5786
     username = "admin"
     password = read("env:DATABASE_PASSWORD") 
     dbName = "myapp"
}

А также написать генератор для автоматизации изменения номера порта для четырёх разных СУБД:


   import "Application.pkl"
 
   hidden db: Application.Database = new {
     host = "localhost"
     username = "admin"
     password = read("env:DATABASE_PASSWORD")
     dbName = "myapp"
   }

   sidecars {
     for (offset in List(0, 1, 2, 3)) {
       (db) {
         port = 6000 + offset
       }
     }
   }

При желании можно экспортировать конфигурацию в другом формате, например, YAML:


   sidecars:
   - username: admin
     password: hunter2
     host: localhost
     port: 6000
     dbName: myapp 
   - username: admin
     password: hunter2
     host: localhost
     port: 6001
     dbName: myapp
   - username: admin
     password: hunter2
     host: localhost
     port: 6002
     dbName: myapp
   - username: admin
     password: hunter2
     host: localhost
     port: 6003
     dbName: myapp

Схема конфигурации на языке Pkl также может быть преобразована в классы или структуры для встраивания в код приложения. Например, можно сгенерировать привязку для Kotlin:


   import kotlin.Int
   import kotlin.Long
   import kotlin.String

   data class Application(
     val hostname: String,
     val port: Int,
     val environment: Environment,
     val database: Database
   ) {

     data class Database(
       val username: String,
       val password: String,
       val host: String,
       val port: Int,
       val dbName: String
     )

     enum class Environment(
       val value: String
     ) {
       DEV("dev"),
       QA("qa"),
       PROD("prod");
       override fun toString() = value
     } 
   }

Источник: http://www.opennet.ru/opennews/art.shtml?num=60549