mirror of
https://github.com/enso-org/enso.git
synced 2024-11-24 00:27:16 +03:00
Add support for the component groups syntax (#3235)
This commit is contained in:
parent
3905698b41
commit
e6d9b5741d
@ -14,13 +14,13 @@ import org.enso.pkg.{Contact, PackageManager, Template}
|
||||
import org.enso.polyglot.{LanguageInfo, Module, PolyglotContext}
|
||||
import org.enso.version.VersionDescription
|
||||
import org.graalvm.polyglot.PolyglotException
|
||||
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
import java.util.UUID
|
||||
|
||||
import scala.Console.err
|
||||
import scala.jdk.CollectionConverters._
|
||||
import scala.util.Try
|
||||
import scala.util.{Failure, Success, Try}
|
||||
import scala.util.control.NonFatal
|
||||
|
||||
/** The main CLI entry point class. */
|
||||
@ -509,16 +509,20 @@ object Main {
|
||||
enableAutoParallelism = enableAutoParallelism
|
||||
)
|
||||
if (projectMode) {
|
||||
val pkg = PackageManager.Default.fromDirectory(file)
|
||||
val main = pkg.map(_.mainFile)
|
||||
if (!main.exists(_.exists())) {
|
||||
println("Main file does not exist.")
|
||||
context.context.close()
|
||||
exitFail()
|
||||
PackageManager.Default.loadPackage(file) match {
|
||||
case Success(pkg) =>
|
||||
val main = pkg.mainFile
|
||||
if (!main.exists()) {
|
||||
println("Main file does not exist.")
|
||||
context.context.close()
|
||||
exitFail()
|
||||
}
|
||||
val mainModuleName = pkg.moduleNameForFile(pkg.mainFile).toString
|
||||
runPackage(context, mainModuleName, file)
|
||||
case Failure(ex) =>
|
||||
println(ex.getMessage)
|
||||
exitFail()
|
||||
}
|
||||
val mainFile = main.get
|
||||
val mainModuleName = pkg.get.moduleNameForFile(mainFile).toString
|
||||
runPackage(context, mainModuleName, file)
|
||||
} else {
|
||||
runSingleFile(context, file)
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ case class LibraryName(namespace: String, name: String) {
|
||||
/** The qualified name of the library consists of its prefix and name
|
||||
* separated with a dot.
|
||||
*/
|
||||
def qualifiedName: String = s"$namespace.$name"
|
||||
def qualifiedName: String = s"$namespace${LibraryName.separator}$name"
|
||||
|
||||
/** @inheritdoc */
|
||||
override def toString: String = qualifiedName
|
||||
@ -36,7 +36,7 @@ object LibraryName {
|
||||
libraryName.toString.asJson
|
||||
}
|
||||
|
||||
private val separator = '.'
|
||||
val separator = '.'
|
||||
|
||||
/** Creates a [[LibraryName]] from its string representation.
|
||||
*
|
||||
|
293
lib/scala/pkg/src/main/scala/org/enso/pkg/ComponentGroup.scala
Normal file
293
lib/scala/pkg/src/main/scala/org/enso/pkg/ComponentGroup.scala
Normal file
@ -0,0 +1,293 @@
|
||||
package org.enso.pkg
|
||||
|
||||
import io.circe._
|
||||
import io.circe.syntax._
|
||||
import org.enso.editions.LibraryName
|
||||
|
||||
/** The description of component groups provided by the package.
|
||||
*
|
||||
* @param newGroups the list of component groups provided by the package
|
||||
* @param extendedGroups the list of component groups that this package extends
|
||||
*/
|
||||
case class ComponentGroups(
|
||||
newGroups: List[ComponentGroup],
|
||||
extendedGroups: List[ExtendedComponentGroup]
|
||||
)
|
||||
object ComponentGroups {
|
||||
|
||||
/** Empty component groups. */
|
||||
val empty: ComponentGroups =
|
||||
ComponentGroups(List(), List())
|
||||
|
||||
/** Fields for use when serializing the [[ComponentGroups]]. */
|
||||
private object Fields {
|
||||
val New = "new"
|
||||
val Extends = "extends"
|
||||
}
|
||||
|
||||
/** [[Encoder]] instance for the [[ComponentGroups]]. */
|
||||
implicit val encoder: Encoder[ComponentGroups] = { componentGroups =>
|
||||
val newGroups = Option.unless(componentGroups.newGroups.isEmpty)(
|
||||
Fields.New -> componentGroups.newGroups.asJson
|
||||
)
|
||||
val extendsGroups = Option.unless(componentGroups.extendedGroups.isEmpty)(
|
||||
Fields.Extends -> componentGroups.extendedGroups.asJson
|
||||
)
|
||||
Json.obj(newGroups.toSeq ++ extendsGroups.toSeq: _*)
|
||||
}
|
||||
|
||||
/** [[Decoder]] instance for the [[ComponentGroups]]. */
|
||||
implicit val decoder: Decoder[ComponentGroups] = { json =>
|
||||
for {
|
||||
newGroups <- json.getOrElse[List[ComponentGroup]](Fields.New)(List())
|
||||
extendsGroups <-
|
||||
json.getOrElse[List[ExtendedComponentGroup]](Fields.Extends)(List())
|
||||
} yield ComponentGroups(newGroups, extendsGroups)
|
||||
}
|
||||
}
|
||||
|
||||
/** The definition of a single component group.
|
||||
*
|
||||
* @param module the module name
|
||||
* @param color the component group color
|
||||
* @param icon the component group icon
|
||||
* @param exports the list of components provided by this component group
|
||||
*/
|
||||
case class ComponentGroup(
|
||||
module: ModuleName,
|
||||
color: Option[String],
|
||||
icon: Option[String],
|
||||
exports: Seq[Component]
|
||||
)
|
||||
object ComponentGroup {
|
||||
|
||||
/** Fields for use when serializing the [[ComponentGroup]]. */
|
||||
private object Fields {
|
||||
val Module = "module"
|
||||
val Color = "color"
|
||||
val Icon = "icon"
|
||||
val Exports = "exports"
|
||||
}
|
||||
|
||||
/** [[Encoder]] instance for the [[ComponentGroup]]. */
|
||||
implicit val encoder: Encoder[ComponentGroup] = { componentGroup =>
|
||||
val color = componentGroup.color.map(Fields.Color -> _.asJson)
|
||||
val icon = componentGroup.icon.map(Fields.Icon -> _.asJson)
|
||||
val exports = Option.unless(componentGroup.exports.isEmpty)(
|
||||
Fields.Exports -> componentGroup.exports.asJson
|
||||
)
|
||||
Json.obj(
|
||||
(Fields.Module -> componentGroup.module.asJson) +:
|
||||
(color.toSeq ++ icon.toSeq ++ exports.toSeq): _*
|
||||
)
|
||||
}
|
||||
|
||||
/** [[Decoder]] instance for the [[ComponentGroup]]. */
|
||||
implicit val decoder: Decoder[ComponentGroup] = { json =>
|
||||
for {
|
||||
name <- ConfigCodecs
|
||||
.getFromObject[ModuleName](
|
||||
"component group name",
|
||||
Fields.Module,
|
||||
json
|
||||
)
|
||||
color <- json.get[Option[String]](Fields.Color)
|
||||
icon <- json.get[Option[String]](Fields.Icon)
|
||||
exports <- json.getOrElse[List[Component]](Fields.Exports)(List())
|
||||
} yield ComponentGroup(name, color, icon, exports)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** The definition of a component group that extends an existing one.
|
||||
*
|
||||
* @param module the reference to the extended component group
|
||||
* @param color the component group color
|
||||
* @param icon the component group icon
|
||||
* @param exports the list of components provided by this component group
|
||||
*/
|
||||
case class ExtendedComponentGroup(
|
||||
module: ModuleReference,
|
||||
color: Option[String],
|
||||
icon: Option[String],
|
||||
exports: Seq[Component]
|
||||
)
|
||||
object ExtendedComponentGroup {
|
||||
|
||||
/** Fields for use when serializing the [[ExtendedComponentGroup]]. */
|
||||
private object Fields {
|
||||
val Module = "module"
|
||||
val Color = "color"
|
||||
val Icon = "icon"
|
||||
val Exports = "exports"
|
||||
}
|
||||
|
||||
/** [[Encoder]] instance for the [[ExtendedComponentGroup]]. */
|
||||
implicit val encoder: Encoder[ExtendedComponentGroup] = {
|
||||
extendedComponentGroup =>
|
||||
val color = extendedComponentGroup.color.map(Fields.Color -> _.asJson)
|
||||
val icon = extendedComponentGroup.icon.map(Fields.Icon -> _.asJson)
|
||||
val exports = Option.unless(extendedComponentGroup.exports.isEmpty)(
|
||||
Fields.Exports -> extendedComponentGroup.exports.asJson
|
||||
)
|
||||
Json.obj(
|
||||
(Fields.Module -> extendedComponentGroup.module.asJson) +:
|
||||
(color.toSeq ++ icon.toSeq ++ exports.toSeq): _*
|
||||
)
|
||||
}
|
||||
|
||||
/** [[Decoder]] instance for the [[ExtendedComponentGroup]]. */
|
||||
implicit val decoder: Decoder[ExtendedComponentGroup] = { json =>
|
||||
for {
|
||||
reference <- ConfigCodecs
|
||||
.getFromObject[ModuleReference](
|
||||
"extended component group reference",
|
||||
Fields.Module,
|
||||
json
|
||||
)
|
||||
color <- json.get[Option[String]](Fields.Color)
|
||||
icon <- json.get[Option[String]](Fields.Icon)
|
||||
exports <- json.getOrElse[List[Component]](Fields.Exports)(List())
|
||||
} yield ExtendedComponentGroup(reference, color, icon, exports)
|
||||
}
|
||||
}
|
||||
|
||||
/** A single component of a component group.
|
||||
*
|
||||
* @param name the component name
|
||||
* @param shortcut the component shortcut
|
||||
*/
|
||||
case class Component(name: String, shortcut: Option[Shortcut])
|
||||
object Component {
|
||||
|
||||
object Fields {
|
||||
val Name = "name"
|
||||
val Shortcut = "shortcut"
|
||||
}
|
||||
|
||||
/** [[Encoder]] instance for the [[Component]]. */
|
||||
implicit val encoder: Encoder[Component] = { component =>
|
||||
component.shortcut match {
|
||||
case Some(shortcut) =>
|
||||
Json.obj(
|
||||
Fields.Name -> component.name.asJson,
|
||||
Fields.Shortcut -> shortcut.asJson
|
||||
)
|
||||
case None =>
|
||||
component.name.asJson
|
||||
}
|
||||
}
|
||||
|
||||
/** [[Decoder]] instance for the [[Component]]. */
|
||||
implicit val decoder: Decoder[Component] = { json =>
|
||||
json.as[String] match {
|
||||
case Right(name) =>
|
||||
Right(Component(name, None))
|
||||
case Left(_) =>
|
||||
for {
|
||||
name <- ConfigCodecs
|
||||
.getFromObject[String]("component name", Fields.Name, json)
|
||||
shortcut <- json.getOrElse[Option[Shortcut]](Fields.Shortcut)(None)
|
||||
} yield Component(name, shortcut)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** The shortcut reference to the component.
|
||||
*
|
||||
* @param key the shortcut key combination
|
||||
*/
|
||||
case class Shortcut(key: String)
|
||||
object Shortcut {
|
||||
|
||||
/** [[Encoder]] instance for the [[Shortcut]]. */
|
||||
implicit val encoder: Encoder[Shortcut] = { shortcut =>
|
||||
shortcut.key.asJson
|
||||
}
|
||||
|
||||
/** [[Decoder]] instance for the [[Shortcut]]. */
|
||||
implicit val decoder: Decoder[Shortcut] = { json =>
|
||||
ConfigCodecs.getScalar("shortcut", json).map(Shortcut(_))
|
||||
}
|
||||
}
|
||||
|
||||
/** The reference to a module.
|
||||
*
|
||||
* @param libraryName the qualified name of a library where the module is defined
|
||||
* @param moduleName the module name
|
||||
*/
|
||||
case class ModuleReference(
|
||||
libraryName: LibraryName,
|
||||
moduleName: Option[ModuleName]
|
||||
)
|
||||
object ModuleReference {
|
||||
|
||||
private def toModuleString(moduleReference: ModuleReference): String = {
|
||||
val libraryName =
|
||||
s"${moduleReference.libraryName.namespace}${LibraryName.separator}${moduleReference.libraryName.name}"
|
||||
moduleReference.moduleName.fold(libraryName) { moduleName =>
|
||||
s"$libraryName${LibraryName.separator}${moduleName.name}"
|
||||
}
|
||||
}
|
||||
|
||||
/** [[Encoder]] instance for the [[ModuleReference]]. */
|
||||
implicit val encoder: Encoder[ModuleReference] = { moduleReference =>
|
||||
toModuleString(moduleReference).asJson
|
||||
}
|
||||
|
||||
/** [[Decoder]] instance for the [[ModuleReference]]. */
|
||||
implicit val decoder: Decoder[ModuleReference] = { json =>
|
||||
json.as[String].flatMap { moduleString =>
|
||||
moduleString.split(LibraryName.separator).toList match {
|
||||
case namespace :: name :: module =>
|
||||
Right(
|
||||
ModuleReference(
|
||||
LibraryName(namespace, name),
|
||||
ModuleName.fromComponents(module)
|
||||
)
|
||||
)
|
||||
case _ =>
|
||||
Left(
|
||||
DecodingFailure(
|
||||
s"Failed to decode '$moduleString' as module reference. " +
|
||||
s"Module reference should consist of a namespace (author), " +
|
||||
s"library name and a module name (e.g. Standard.Base.Data).",
|
||||
json.history
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** The module name.
|
||||
*
|
||||
* @param name the module name
|
||||
*/
|
||||
case class ModuleName(name: String)
|
||||
object ModuleName {
|
||||
|
||||
def fromComponents(items: List[String]): Option[ModuleName] =
|
||||
Option.unless(items.isEmpty)(ModuleName(items.mkString(".")))
|
||||
|
||||
/** [[Encoder]] instance for the [[ModuleName]]. */
|
||||
implicit val encoder: Encoder[ModuleName] = { moduleName =>
|
||||
moduleName.name.asJson
|
||||
}
|
||||
|
||||
/** [[Decoder]] instance for the [[ModuleName]]. */
|
||||
implicit val decoder: Decoder[ModuleName] = { json =>
|
||||
json.as[String] match {
|
||||
case Left(_) =>
|
||||
Left(
|
||||
DecodingFailure(
|
||||
"Failed to decode component group name.",
|
||||
json.history
|
||||
)
|
||||
)
|
||||
case Right(name) =>
|
||||
Right(ModuleName(name))
|
||||
}
|
||||
}
|
||||
}
|
@ -99,6 +99,8 @@ object Contact {
|
||||
* @param preferLocalLibraries specifies if library resolution should prefer
|
||||
* local libraries over what is defined in the
|
||||
* edition
|
||||
* @param componentGroups the description of component groups provided by this
|
||||
* package
|
||||
* @param originalJson a Json object holding the original values that this
|
||||
* Config was created from, used to preserve configuration
|
||||
* keys that are not known
|
||||
@ -112,6 +114,7 @@ case class Config(
|
||||
maintainers: List[Contact],
|
||||
edition: Option[Editions.RawEdition],
|
||||
preferLocalLibraries: Boolean,
|
||||
componentGroups: Either[DecodingFailure, ComponentGroups],
|
||||
originalJson: JsonObject = JsonObject()
|
||||
) {
|
||||
|
||||
@ -134,6 +137,7 @@ object Config {
|
||||
val maintainer: String = "maintainers"
|
||||
val edition: String = "edition"
|
||||
val preferLocalLibraries = "prefer-local-libraries"
|
||||
val componentGroups = "component-groups"
|
||||
}
|
||||
|
||||
implicit val decoder: Decoder[Config] = { json =>
|
||||
@ -166,17 +170,25 @@ object Config {
|
||||
error => DecodingFailure(error, json.history)
|
||||
}
|
||||
originals <- json.as[JsonObject]
|
||||
} yield Config(
|
||||
name = name,
|
||||
namespace = namespace,
|
||||
version = version,
|
||||
license = license,
|
||||
authors = author,
|
||||
maintainers = maintainer,
|
||||
edition = finalEdition,
|
||||
preferLocalLibraries = preferLocal,
|
||||
originalJson = originals
|
||||
)
|
||||
} yield {
|
||||
val componentGroups =
|
||||
json.getOrElse[ComponentGroups](JsonFields.componentGroups)(
|
||||
ComponentGroups.empty
|
||||
)
|
||||
|
||||
Config(
|
||||
name = name,
|
||||
namespace = namespace,
|
||||
version = version,
|
||||
license = license,
|
||||
authors = author,
|
||||
maintainers = maintainer,
|
||||
edition = finalEdition,
|
||||
preferLocalLibraries = preferLocal,
|
||||
componentGroups = componentGroups,
|
||||
originalJson = originals
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
implicit val encoder: Encoder[Config] = { config =>
|
||||
@ -189,6 +201,12 @@ object Config {
|
||||
}
|
||||
.map(JsonFields.edition -> _)
|
||||
|
||||
val componentGroups = config.componentGroups.toOption.flatMap { value =>
|
||||
Option.unless(value.newGroups.isEmpty && value.extendedGroups.isEmpty)(
|
||||
JsonFields.componentGroups -> value.asJson
|
||||
)
|
||||
}
|
||||
|
||||
val overrides = Seq(
|
||||
JsonFields.name -> config.name.asJson,
|
||||
JsonFields.namespace -> config.namespace.asJson,
|
||||
@ -196,7 +214,7 @@ object Config {
|
||||
JsonFields.license -> config.license.asJson,
|
||||
JsonFields.author -> config.authors.asJson,
|
||||
JsonFields.maintainer -> config.maintainers.asJson
|
||||
) ++ edition.toSeq
|
||||
) ++ edition.toSeq ++ componentGroups.toSeq
|
||||
|
||||
val preferLocalOverride =
|
||||
if (config.preferLocalLibraries)
|
||||
|
74
lib/scala/pkg/src/main/scala/org/enso/pkg/ConfigCodecs.scala
Normal file
74
lib/scala/pkg/src/main/scala/org/enso/pkg/ConfigCodecs.scala
Normal file
@ -0,0 +1,74 @@
|
||||
package org.enso.pkg
|
||||
|
||||
import io.circe._
|
||||
import io.circe.syntax._
|
||||
|
||||
/** A collection of utility codecs used in the [[Config]]. */
|
||||
object ConfigCodecs {
|
||||
|
||||
/** The common decoding failure.
|
||||
*
|
||||
* @param entity the name of decoded entity
|
||||
* @param history the list of JSON cursor operations
|
||||
*/
|
||||
private def decodingFailure(
|
||||
entity: String,
|
||||
history: List[CursorOp]
|
||||
): DecodingFailure =
|
||||
DecodingFailure(s"Failed to decode $entity", history)
|
||||
|
||||
/** Try to decode the entity `A` from a JSON object.
|
||||
*
|
||||
* The entity can be encoded either in the first key of the JSON object with
|
||||
* the Null value,
|
||||
* {{{
|
||||
* { entity: null }
|
||||
* }}}
|
||||
*
|
||||
* or as a value of the provided `keyName` argument
|
||||
*
|
||||
* {{{
|
||||
* { `keyName`: entity }
|
||||
* }}}
|
||||
*
|
||||
* @param entity the name of decoded entity that is used in error reporting
|
||||
* @param keyName the key of the JSON object that contains the entity
|
||||
* @param cursor the current focus in the JSON document
|
||||
*/
|
||||
def getFromObject[A: Decoder](
|
||||
entity: String,
|
||||
keyName: String,
|
||||
cursor: HCursor
|
||||
): Decoder.Result[A] =
|
||||
cursor.keys match {
|
||||
case Some(keys) if keys.nonEmpty =>
|
||||
cursor
|
||||
.get[A](keyName)
|
||||
.orElse {
|
||||
cursor.get[Json](keys.head).flatMap { json =>
|
||||
if (json.isNull) {
|
||||
Decoder[A].decodeJson(keys.head.asJson)
|
||||
} else {
|
||||
Left(decodingFailure(entity, cursor.history))
|
||||
}
|
||||
}
|
||||
}
|
||||
case _ =>
|
||||
Left(decodingFailure(entity, cursor.history))
|
||||
}
|
||||
|
||||
/** Get the scalar value of the provided JSON element.
|
||||
*
|
||||
* @param entity the name of decoded entity
|
||||
* @param cursor the current focus in the JSON document
|
||||
*/
|
||||
def getScalar(entity: String, cursor: HCursor): Decoder.Result[String] =
|
||||
cursor.value.fold(
|
||||
jsonNull = Left(decodingFailure(entity, cursor.history)),
|
||||
jsonBoolean = value => Right(value.toString),
|
||||
jsonNumber = value => Right(value.toString),
|
||||
jsonString = value => Right(value),
|
||||
jsonArray = _ => Left(decodingFailure(entity, cursor.history)),
|
||||
jsonObject = _ => Left(decodingFailure(entity, cursor.history))
|
||||
)
|
||||
}
|
@ -224,7 +224,8 @@ class PackageManager[F](implicit val fileSystem: FileSystem[F]) {
|
||||
edition: Option[Editions.RawEdition] = None,
|
||||
authors: List[Contact] = List(),
|
||||
maintainers: List[Contact] = List(),
|
||||
license: String = ""
|
||||
license: String = "",
|
||||
componentGroups: ComponentGroups = ComponentGroups.empty
|
||||
): Package[F] = {
|
||||
val config = Config(
|
||||
name = NameValidation.normalizeName(name),
|
||||
@ -234,7 +235,8 @@ class PackageManager[F](implicit val fileSystem: FileSystem[F]) {
|
||||
authors = authors,
|
||||
edition = edition,
|
||||
preferLocalLibraries = true,
|
||||
maintainers = maintainers
|
||||
maintainers = maintainers,
|
||||
componentGroups = Right(componentGroups)
|
||||
)
|
||||
create(root, config, template)
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
package org.enso.pkg
|
||||
|
||||
import io.circe.{Json, JsonObject}
|
||||
import cats.Show
|
||||
import io.circe.{DecodingFailure, Json, JsonObject}
|
||||
import nl.gn0s1s.bump.SemVer
|
||||
import org.enso.editions.LibraryName
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
import org.scalatest.wordspec.AnyWordSpec
|
||||
import org.scalatest.{Inside, OptionValues}
|
||||
@ -11,6 +13,7 @@ class ConfigSpec
|
||||
with Matchers
|
||||
with Inside
|
||||
with OptionValues {
|
||||
|
||||
"Config" should {
|
||||
"preserve unknown keys when deserialized and serialized again" in {
|
||||
val original = Json.obj(
|
||||
@ -42,7 +45,8 @@ class ConfigSpec
|
||||
Contact(Some("B"), None),
|
||||
Contact(None, Some("c@example.com"))
|
||||
),
|
||||
preferLocalLibraries = true
|
||||
preferLocalLibraries = true,
|
||||
componentGroups = Right(ComponentGroups.empty)
|
||||
)
|
||||
val deserialized = Config.fromYaml(config.toYaml).get
|
||||
val withoutJson = deserialized.copy(originalJson = JsonObject())
|
||||
@ -90,4 +94,215 @@ class ConfigSpec
|
||||
serialized should include("edition: '2020.1'")
|
||||
}
|
||||
}
|
||||
|
||||
"Component groups" should {
|
||||
|
||||
"correctly de-serialize and serialize back the components syntax" in {
|
||||
val config =
|
||||
"""name: FooBar
|
||||
|component-groups:
|
||||
| new:
|
||||
| - Group 1:
|
||||
| color: '#C047AB'
|
||||
| icon: icon-name
|
||||
| exports:
|
||||
| - foo:
|
||||
| shortcut: f
|
||||
| - bar
|
||||
| extends:
|
||||
| - Standard.Base.Group 2:
|
||||
| exports:
|
||||
| - bax
|
||||
|""".stripMargin
|
||||
val parsed = Config.fromYaml(config).get
|
||||
|
||||
val expectedComponentGroups = ComponentGroups(
|
||||
newGroups = List(
|
||||
ComponentGroup(
|
||||
module = ModuleName("Group 1"),
|
||||
color = Some("#C047AB"),
|
||||
icon = Some("icon-name"),
|
||||
exports = List(
|
||||
Component("foo", Some(Shortcut("f"))),
|
||||
Component("bar", None)
|
||||
)
|
||||
)
|
||||
),
|
||||
extendedGroups = List(
|
||||
ExtendedComponentGroup(
|
||||
module = ModuleReference(
|
||||
LibraryName("Standard", "Base"),
|
||||
Some(ModuleName("Group 2"))
|
||||
),
|
||||
color = None,
|
||||
icon = None,
|
||||
exports = List(Component("bax", None))
|
||||
)
|
||||
)
|
||||
)
|
||||
parsed.componentGroups shouldEqual Right(expectedComponentGroups)
|
||||
|
||||
val serialized = parsed.toYaml
|
||||
serialized should include(
|
||||
"""component-groups:
|
||||
| new:
|
||||
| - module: Group 1
|
||||
| color: '#C047AB'
|
||||
| icon: icon-name
|
||||
| exports:
|
||||
| - name: foo
|
||||
| shortcut: f
|
||||
| - bar
|
||||
| extends:
|
||||
| - module: Standard.Base.Group 2
|
||||
| exports:
|
||||
| - bax""".stripMargin.linesIterator.mkString("\n")
|
||||
)
|
||||
}
|
||||
|
||||
"correctly de-serialize empty components" in {
|
||||
val config =
|
||||
"""name: FooBar
|
||||
|component-groups:
|
||||
|""".stripMargin
|
||||
val parsed = Config.fromYaml(config).get
|
||||
|
||||
parsed.componentGroups shouldEqual Right(ComponentGroups.empty)
|
||||
}
|
||||
|
||||
"allow unknown keys in component groups" in {
|
||||
val config =
|
||||
"""name: FooBar
|
||||
|component-groups:
|
||||
| foo:
|
||||
| - Group 1:
|
||||
| exports:
|
||||
| - bax
|
||||
|""".stripMargin
|
||||
val parsed = Config.fromYaml(config).get
|
||||
|
||||
parsed.componentGroups shouldEqual Right(ComponentGroups.empty)
|
||||
}
|
||||
|
||||
"fail to de-serialize invalid extended modules" in {
|
||||
val config =
|
||||
"""name: FooBar
|
||||
|component-groups:
|
||||
| extends:
|
||||
| - Group 1:
|
||||
| exports:
|
||||
| - bax
|
||||
|""".stripMargin
|
||||
val parsed = Config.fromYaml(config).get
|
||||
|
||||
parsed.componentGroups match {
|
||||
case Left(f: DecodingFailure) =>
|
||||
Show[DecodingFailure].show(f) should include(
|
||||
"Failed to decode 'Group 1' as module reference"
|
||||
)
|
||||
case unexpected =>
|
||||
fail(s"Unexpected result: $unexpected")
|
||||
}
|
||||
}
|
||||
|
||||
"correctly de-serialize shortcuts" in {
|
||||
val config =
|
||||
"""name: FooBar
|
||||
|component-groups:
|
||||
| new:
|
||||
| - Group 1:
|
||||
| exports:
|
||||
| - foo:
|
||||
| shortcut: f
|
||||
| - bar:
|
||||
| shortcut: fgh
|
||||
| - baz:
|
||||
| shortcut: 0
|
||||
| - quux:
|
||||
| shortcut:
|
||||
| - hmmm:
|
||||
|""".stripMargin
|
||||
val parsed = Config.fromYaml(config).get
|
||||
val expectedComponentGroups = ComponentGroups(
|
||||
newGroups = List(
|
||||
ComponentGroup(
|
||||
module = ModuleName("Group 1"),
|
||||
color = None,
|
||||
icon = None,
|
||||
exports = List(
|
||||
Component("foo", Some(Shortcut("f"))),
|
||||
Component("bar", Some(Shortcut("fgh"))),
|
||||
Component("baz", Some(Shortcut("0"))),
|
||||
Component("quux", None),
|
||||
Component("hmmm", None)
|
||||
)
|
||||
)
|
||||
),
|
||||
extendedGroups = List()
|
||||
)
|
||||
|
||||
parsed.componentGroups shouldEqual Right(expectedComponentGroups)
|
||||
}
|
||||
|
||||
"fail to de-serialize invalid shortcuts" in {
|
||||
val config =
|
||||
"""name: FooBar
|
||||
|component-groups:
|
||||
| new:
|
||||
| - Group 1:
|
||||
| exports:
|
||||
| - foo:
|
||||
| shortcut: []
|
||||
|""".stripMargin
|
||||
val parsed = Config.fromYaml(config).get
|
||||
parsed.componentGroups match {
|
||||
case Left(f: DecodingFailure) =>
|
||||
Show[DecodingFailure].show(f) should include(
|
||||
"Failed to decode shortcut"
|
||||
)
|
||||
case unexpected =>
|
||||
fail(s"Unexpected result: $unexpected")
|
||||
}
|
||||
}
|
||||
|
||||
"fail to de-serialize invalid component groups" in {
|
||||
val config =
|
||||
"""name: FooBar
|
||||
|component-groups:
|
||||
| new:
|
||||
| - exports:
|
||||
| - name: foo
|
||||
|""".stripMargin
|
||||
val parsed = Config.fromYaml(config).get
|
||||
parsed.componentGroups match {
|
||||
case Left(f: DecodingFailure) =>
|
||||
Show[DecodingFailure].show(f) should include(
|
||||
"Failed to decode component group name"
|
||||
)
|
||||
case unexpected =>
|
||||
fail(s"Unexpected result: $unexpected")
|
||||
}
|
||||
}
|
||||
|
||||
"fail to de-serialize invalid components" in {
|
||||
val config =
|
||||
"""name: FooBar
|
||||
|component-groups:
|
||||
| new:
|
||||
| - Group 1:
|
||||
| exports:
|
||||
| - one: two
|
||||
|""".stripMargin
|
||||
val parsed = Config.fromYaml(config).get
|
||||
parsed.componentGroups match {
|
||||
case Left(f: DecodingFailure) =>
|
||||
Show[DecodingFailure].show(f) should include(
|
||||
"Failed to decode component name"
|
||||
)
|
||||
case unexpected =>
|
||||
fail(s"Unexpected result: $unexpected")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user