Skip to content

Commit

Permalink
Adding Morphir Path and QName (#16)
Browse files Browse the repository at this point in the history
* adding Morphir Path and QName
  • Loading branch information
michelchan authored Feb 9, 2022
1 parent 3f66737 commit 71009c2
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 6 deletions.
3 changes: 3 additions & 0 deletions morphir-ir/shared/src/main/scala/zio/morphir/ir/Name.scala
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ final case class Name private (toList: List[String]) extends AnyVal { self =>
def mkString(f: String => String)(sep: String): String =
toList.map(f).mkString(sep)

def toUpperCase: String = mkString(part => part.toUpperCase)("")

def toLowerCase: String =
mkString(part => part.toLowerCase)("")

Expand All @@ -69,6 +71,7 @@ final case class Name private (toList: List[String]) extends AnyVal { self =>
.mkString("")

}

object Name {

val empty: Name = Name(Nil)
Expand Down
37 changes: 37 additions & 0 deletions morphir-ir/shared/src/main/scala/zio/morphir/ir/Path.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package zio.morphir.ir
import zio.Chunk
import zio.morphir.ir.PackageModule.PackageAndModulePath

import scala.annotation.tailrec

final case class Path(segments: Chunk[Name]) { self =>

/** Constructs a new path by combining this path with the given name. */
Expand All @@ -18,8 +20,43 @@ final case class Path(segments: Chunk[Name]) { self =>
def isEmpty: Boolean = segments.isEmpty
def zip(other: Path): (Path, Path) = (self, other)

def toList: List[Name] = segments.toList

def toString(f: Name => String, separator: String): String =
segments.map(f).mkString(separator)

/** Checks if this path is a prefix of provided path */
def isPrefixOf(path: Path): Boolean = Path.isPrefixOf(self, path)
}

object Path {
val empty: Path = Path(Chunk.empty)

private def wrap(value: List[Name]): Path = Path(Chunk.fromIterable(value))

def fromString(str: String): Path = {
val separatorRegex = """[^\w\s]+""".r
wrap(separatorRegex.split(str).map(Name.fromString).toList)
}

def toString(f: Name => String, separator: String, path: Path): String =
path.toString(f, separator)

@inline def fromList(names: List[Name]): Path = wrap(names)

@inline def toList(path: Path): List[Name] = path.segments.toList

/** Checks if the first provided path is a prefix of the second path */
@tailrec
def isPrefixOf(prefix: Path, path: Path): Boolean = (prefix.toList, path.toList) match {
case (Nil, _) => true
case (_, Nil) => false
case (prefixHead :: prefixTail, pathHead :: pathTail) =>
if (prefixHead == pathHead)
isPrefixOf(
Path.fromList(prefixTail),
Path.fromList(pathTail)
)
else false
}
}
17 changes: 15 additions & 2 deletions morphir-ir/shared/src/main/scala/zio/morphir/ir/QName.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,26 @@ final case class QName(modulePath: Path, localName: Name) {
@inline def toTuple: (Path, Name) = (modulePath, localName)

override def toString: String =
if (modulePath.isEmpty) localName.toString
else modulePath.toString + "." + localName.toString
modulePath.toString(Name.toTitleCase, ".") + ":" + localName.toCamelCase

}

object QName {
def toTuple(qName: QName): (Path, Name) = qName.toTuple
def fromTuple(tuple: (Path, Name)): QName = QName(tuple._1, tuple._2)

def fromName(modulePath: Path, localName: Name): QName = QName(modulePath, localName)

def getLocalName(qname: QName): Name = qname.localName
def getModulePath(qname: QName): Path = qname.modulePath

def toString(qName: QName): String = qName.toString

def fromString(str: String): Option[QName] = {
str.split(":") match {
case Array(packageNameString, localNameString) =>
Some(QName(Path.fromString(packageNameString), Name.fromString(localNameString)))
case _ => None
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package zio.morphir.ir

import zio.morphir.ir.testing.MorphirBaseSpec
import zio.test.*

object NameSpec extends MorphirBaseSpec {
def spec = suite("Name")(
suite("Create a Name from a string and check that:")(
Expand Down
71 changes: 67 additions & 4 deletions morphir-ir/shared/src/test/scala/zio/morphir/ir/PathSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,74 @@ import testing.MorphirBaseSpec

object PathSpec extends MorphirBaseSpec {
def spec = suite("Path")(
test("It can be constructed from names")(
assertTrue(
Name("Org") / Name("Finos") == Path(Chunk(Name("Org"), Name("Finos"))),
Name("Alpha") / Name("Beta") / Name("Gamma") == Path(Chunk(Name("Alpha"), Name("Beta"), Name("Gamma")))
suite("Creating a Path from a String")(
test("It can be constructed from a simple string") {
assertTrue(Path.fromString("Person") == Path(Chunk(Name.fromString("person"))))
},
test("It can be constructed when given a dotted string") {
assertTrue(
Path.fromString("blog.Author") == Path(
Chunk(Name.fromList(List("blog")), Name.fromList(List("author")))
)
)
}
),
suite("Transforming a Path into a String")(
test("Paths with period and TitleCase") {
val input = Path(
Chunk(
Name("foo", "bar"),
Name("baz")
)
)
assertTrue(Path.toString(Name.toTitleCase, ".", input) == "FooBar.Baz")
},
test("Paths with slash and Snake_Case") {
val input = Path(
Chunk(
Name("foo", "bar"),
Name("baz")
)
)
assertTrue(Path.toString(Name.toSnakeCase, "/", input) == "foo_bar/baz")
}
),
suite("Transforming a Path into list of Names")(
test("It can be constructed using toList") {
assertTrue(
Path.toList(Path(Chunk(Name("Com", "Example"), Name("Hello", "World"))))
== List(Name("Com", "Example"), Name("Hello", "World"))
)
}
),
suite("Creating a Path from a Name")(
test("It can be constructed from names")(
assertTrue(
Name("Org") / Name("Finos") == Path(Chunk(Name("Org"), Name("Finos"))),
Name("Alpha") / Name("Beta") / Name("Gamma") == Path(Chunk(Name("Alpha"), Name("Beta"), Name("Gamma")))
)
)
),
suite("Checking if one Path is a prefix of another should:")(
test("""Return true: Given path is "foo/bar" and prefix is "foo" """) {
val sut = Path.fromString("foo/bar")
val prefix = Path.fromString("foo")
val x = Path.isPrefixOf(prefix = prefix, path = sut)
assertTrue(x)
},
test("""Return false: Given path is "foo/foo" and prefix is "bar" """) {
val sut = Path.fromString("foo/foo")
val prefix = Path.fromString("bar")

val x = Path.isPrefixOf(prefix = prefix, path = sut)
assertTrue(!x)
},
test("""Return true: Given equal paths""") {
val sut = Path.fromString("foo/bar/baz")
val prefix = sut
val x = Path.isPrefixOf(prefix = prefix, path = sut)
assertTrue(x)
}
)
)
}
51 changes: 51 additions & 0 deletions morphir-ir/shared/src/test/scala/zio/morphir/ir/QNameSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package zio.morphir.ir

import zio.test.*
import zio.morphir.ir.testing.MorphirBaseSpec

object QNameSpec extends MorphirBaseSpec {
def spec = suite("QName")(
suite("Creating a tuple from QName")(
test("toTuple should provide the Path and Name as a tuple") {
val path = Path.fromString("ice.cream")
val name = Name.fromString("float")
val expected = (path, name)
assertTrue(QName(path, name).toTuple == expected)
}
),
suite("Creating a QName")(
test("Creating a QName with a tuple") {
val path = Path.fromString("friday")
val name = Name.fromString("night")
assertTrue(QName.fromTuple((path, name)) == QName(path, name))
},
test("Creating a QName from a name") {
val path = Path.fromString("blog.Author")
val name = Name.fromString("book")
assertTrue(QName.fromName(path, name) == QName(path, name))
}
),
suite("Fetching values from QName")(
test("localName and path") {
val path = Path.fromString("path")
val name = Name.fromString("name")
assertTrue(QName.getLocalName(QName(path, name)) == name)
assertTrue(QName.getModulePath(QName(path, name)) == path)
}
),
suite("QName and Strings")(
test("Create String from QName") {
val path = Path.fromString("front.page")
val name = Name.fromString("dictionary words")
assertTrue(QName(path, name).toString == "Front.Page:dictionaryWords")
},
test("Create QName from String") {
val str = "Proper.Path:name"
assertTrue(QName.fromString(str) == Some(QName(Path.fromString("Proper.Path"), Name.fromString("name"))))

val str2 = "invalidpathname"
assertTrue(QName.fromString(str2) == None)
}
)
)
}

0 comments on commit 71009c2

Please sign in to comment.