Skip to content

Commit 63cc39f

Browse files
authored
Merge pull request #66 from cucumber/v5.8
Merge work planned for next v5 into v6
2 parents fce66b2 + 8af4eb8 commit 63cc39f

File tree

5 files changed

+478
-13
lines changed

5 files changed

+478
-13
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ See also the [CHANGELOG](https://github.com/cucumber/cucumber-jvm/blob/master/CH
1111

1212
### Added
1313

14+
- [Scala] Conversion methods from `DataTable` to scala types ([#56](https://github.com/cucumber/cucumber-jvm-scala/issues/56) Gaël Jourdan-Weil)
15+
1416
### Changed
1517

1618
### Deprecated

docs/datatables.md

Lines changed: 77 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# DataTables
22

3-
Cucumber Scala support DataTables with Java types.
3+
Cucumber Scala support DataTables with either:
4+
- Scala types using a `DataTable` as step definition argument and implicit conversions by importing `import io.cucumber.scala.Implicits._`
5+
- Java types in the step definitions arguments
6+
7+
**The benefit of using Scala types** if that you will be handling `Option`s instead of potentially `null` values in the Java collections.
48

59
See below the exhaustive list of possible mappings.
610

@@ -10,15 +14,25 @@ See below the exhaustive list of possible mappings.
1014
Given the following table as Map of Map
1115
| | key1 | key2 | key3 |
1216
| row1 | val11 | val12 | val13 |
13-
| row2 | val21 | val22 | val23 |
17+
| row2 | val21 | | val23 |
1418
| row3 | val31 | val32 | val33 |
1519
```
1620

1721
```scala
22+
Given("the following table as Map of Map") { (table: DataTable) =>
23+
val scalaTable: Map[String, Map[String, Option[String]]] = table.asScalaRowColumnMap
24+
// Map(
25+
// "row1" -> Map("key1" -> Some("val11"), "key2" -> Some("val12"), "key3" -> Some("val13")),
26+
// "row2" -> Map("key1" -> Some("val21"), "key2" -> None, "key3" -> Some("val23")),
27+
// "row3" -> Map("key1" -> Some("val31"), "key2" -> Some("val32"), "key3" -> Some("val33"))
28+
// )
29+
}
30+
31+
// Or:
1832
Given("the following table as Map of Map") { (table: JavaMap[String, JavaMap[String, String]]) =>
1933
// Map(
2034
// "row1" -> Map("key1" -> "val11", "key2" -> "val12", "key3" -> "val13"),
21-
// "row2" -> Map("key1" -> "val21", "key2" -> "val22", "key3" -> "val23"),
35+
// "row2" -> Map("key1" -> "val21", "key2" -> null, "key3" -> "val23"),
2236
// "row3" -> Map("key1" -> "val31", "key2" -> "val32", "key3" -> "val33")
2337
// )
2438
}
@@ -30,15 +44,25 @@ Given("the following table as Map of Map") { (table: JavaMap[String, JavaMap[Str
3044
Given the following table as List of Map
3145
| key1 | key2 | key3 |
3246
| val11 | val12 | val13 |
33-
| val21 | val22 | val23 |
47+
| val21 | | val23 |
3448
| val31 | val32 | val33 |
3549
```
3650

3751
```scala
52+
Given("the following table as List of Map") { (table: DataTable) =>
53+
val scalaTable: Seq[Map[String, Option[String]]] = table.asScalaMaps
54+
// Seq(
55+
// Map("key1" -> Some("val11"), "key2" -> Some("val12"), "key3" -> Some("val13")),
56+
// Map("key1" -> Some("val21"), "key2" -> None, "key3" -> Some("val23")),
57+
// Map("key1" -> Some("val31"), "key2" -> Some("val32"), "key3" -> Some("val33"))
58+
// )
59+
}
60+
61+
// Or:
3862
Given("the following table as List of Map") { (table: JavaList[JavaMap[String, String]]) =>
3963
// Seq(
4064
// Map("key1" -> "val11", "key2" -> "val12", "key3" -> "val13"),
41-
// Map("key1" -> "val21", "key2" -> "val22", "key3" -> "val23"),
65+
// Map("key1" -> "val21", "key2" -> null, "key3" -> "val23"),
4266
// Map("key1" -> "val31", "key2" -> "val32", "key3" -> "val33")
4367
// )
4468
}
@@ -49,15 +73,25 @@ Given("the following table as List of Map") { (table: JavaList[JavaMap[String, S
4973
```gherkin
5074
Given the following table as Map of List
5175
| row1 | val11 | val12 | val13 |
52-
| row2 | val21 | val22 | val23 |
76+
| row2 | val21 | | val23 |
5377
| row3 | val31 | val32 | val33 |
5478
```
5579

5680
```scala
81+
Given("the following table as Map of List") { (table: DataTable) =>
82+
val scalaTable: Map[Seq[Option[String]]] = table.asScalaRowMap
83+
// Map(
84+
// "row1" -> Seq(Some("val11"), Some("val12"), Some("val13")),
85+
// "row2" -> Seq(Some("val21"), None, Some("val23")),
86+
// "row3" -> Seq(Some("val31"), Some("val32"), Some("val33"))
87+
// )
88+
}
89+
90+
// Or:
5791
Given("the following table as Map of List") { (table: JavaMap[String, JavaList[String]]) =>
5892
// Map(
5993
// "row1" -> Seq("val11", "val12", "val13"),
60-
// "row2" -> Seq("val21", "val22", "val23"),
94+
// "row2" -> Seq("val21", null, "val23"),
6195
// "row3" -> Seq("val31", "val32", "val33")
6296
// )
6397
}
@@ -69,15 +103,25 @@ Given("the following table as Map of List") { (table: JavaMap[String, JavaList[S
69103
```gherkin
70104
Given the following table as List of List
71105
| val11 | val12 | val13 |
72-
| val21 | val22 | val23 |
106+
| val21 | | val23 |
73107
| val31 | val32 | val33 |
74108
```
75109

76110
```scala
111+
Given("the following table as List of List") { (table: DataTable) =>
112+
val scalaTable: Seq[Seq[Option[String]]] = table.asScalaLists
113+
// Seq(
114+
// Seq(Some("val11"), Some("val12"), Some("val13")),
115+
// Seq(Some("val21"), None, Some("val23")),
116+
// Seq(Some("val31"), Some("val32"), Some("val33"))
117+
// )
118+
}
119+
120+
// Or:
77121
Given("the following table as List of List") { (table: JavaList[JavaList[String]]) =>
78122
// Seq(
79123
// Seq("val11", "val12", "val13"),
80-
// Seq("val21", "val22", "val23"),
124+
// Seq("val21", null, "val23"),
81125
// Seq("val31", "val32", "val33")
82126
// )
83127
}
@@ -88,15 +132,25 @@ Given("the following table as List of List") { (table: JavaList[JavaList[String]
88132
```gherkin
89133
Given the following table as Map
90134
| row1 | val11 |
91-
| row2 | val21 |
135+
| row2 | |
92136
| row3 | val31 |
93137
```
94138

95139
```scala
140+
Given("the following table as Map") { (table: DataTable) =>
141+
val scalaTable: Map[String, Option[String]] = table.asScalaMap[String, String]
142+
// Map(
143+
// "row1" -> Some("val11"),
144+
// "row2" -> None,
145+
// "row3" -> Some("val31")
146+
// )
147+
}
148+
149+
// Or:
96150
Given("the following table as Map") { (table: JavaMap[String, String]) =>
97151
// Map(
98152
// "row1" -> "val11",
99-
// "row2" -> "val21",
153+
// "row2" -> null,
100154
// "row3" -> "val31"
101155
// )
102156
}
@@ -107,15 +161,25 @@ Given("the following table as Map") { (table: JavaMap[String, String]) =>
107161
```gherkin
108162
Given the following table as List
109163
| val11 |
110-
| val21 |
164+
| |
111165
| val31 |
112166
```
113167

114168
```scala
169+
Given("the following table as List") { (table: DataTable) =>
170+
val scalaTable: Seq[Option[String]] = table.asScalaList
171+
// Seq(
172+
// Some("val11"),
173+
// None,
174+
// Some("val31")
175+
// )
176+
}
177+
178+
// Or:
115179
Given("the following table as List") { (table: JavaList[String]) =>
116180
// Seq(
117181
// "val11",
118-
// "val21",
182+
// null,
119183
// "val31"
120184
// )
121185
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package io.cucumber.scala
2+
3+
import io.cucumber.datatable.DataTable
4+
5+
import scala.jdk.CollectionConverters._
6+
import scala.reflect.ClassTag
7+
8+
/**
9+
* Contains implicit helpers for Cucumber Scala users.
10+
*/
11+
object Implicits {
12+
13+
/**
14+
* DataTable extension class providing methods to read a DataTable as Scala types.
15+
* <p>
16+
* <em>Note: we do not filter out null values because users might rely on the keyset in their implementation.</em>
17+
*/
18+
implicit class ScalaDataTable(table: DataTable) {
19+
20+
def asScalaDataTable: ScalaDataTable = this
21+
22+
/**
23+
* Provides a view of the DataTable as a sequence of rows, each row being a key-value map where key is the column name.
24+
* Equivalent of `.asMaps[K,V](classOf[K], classOf[V])` but returned as Scala collection types without `null` values.
25+
*
26+
* @tparam K key type
27+
* @tparam V value type
28+
* @return sequence of rows
29+
*/
30+
def asScalaMaps[K, V](implicit evK: ClassTag[K], evV: ClassTag[V]): Seq[Map[K, Option[V]]] = {
31+
table.asMaps[K, V](evK.runtimeClass, evV.runtimeClass)
32+
.asScala
33+
.map(_.asScala.map(nullToNone).toMap)
34+
.toSeq
35+
}
36+
37+
/**
38+
* Provides a view of the DataTable as a sequence of rows, each row being a key-value map where key is the column name.
39+
* Equivalent of `.asMaps()` but returned as Scala collection types without `null` values.
40+
*
41+
* @return sequence of rows
42+
*/
43+
def asScalaMaps: Seq[Map[String, Option[String]]] = asScalaMaps[String, String]
44+
45+
/**
46+
* Provides a view of the DataTable as a key-value map where key are the first column values.
47+
* Equivalent of `.asMap[K,V](classOf[K],classOf[V])` but returned as Scala collection types without `null` values.
48+
*
49+
* @tparam K key type
50+
* @tparam V value type
51+
* @return key-value map
52+
*/
53+
def asScalaMap[K, V](implicit evK: ClassTag[K], evV: ClassTag[V]): Map[K, Option[V]] = {
54+
table.asMap[K, V](evK.runtimeClass, evV.runtimeClass)
55+
.asScala
56+
.map(nullToNone)
57+
.toMap
58+
}
59+
60+
/**
61+
* Provides a view of the DataTable as a matrix.
62+
* Equivalent of `.asLists[T](classOf[T])` but returned as Scala collection types without `null` values.
63+
*
64+
* @tparam T cell type
65+
* @return matrix
66+
*/
67+
def asScalaLists[T](implicit ev: ClassTag[T]): Seq[Seq[Option[T]]] = {
68+
table.asLists[T](ev.runtimeClass)
69+
.asScala
70+
.map(_.asScala.map(Option.apply).toSeq)
71+
.toSeq
72+
}
73+
74+
/**
75+
* Provides a view of the DataTable as a matrix.
76+
* Equivalent of `.asLists()` but returned as Scala collection types without `null` values.
77+
*
78+
* @return matrix
79+
*/
80+
def asScalaLists: Seq[Seq[Option[String]]] = asScalaLists[String]
81+
82+
/**
83+
* Provides a view of the DataTable as a simple list of values.
84+
* Equivalent of `.asList[T](classOf[T])` but returned as Scala collection types without `null` values.
85+
*
86+
* @tparam T cell type
87+
* @return list of values
88+
*/
89+
def asScalaList[T](implicit ev: ClassTag[T]): Seq[Option[T]] = {
90+
table.asList[T](ev.runtimeClass)
91+
.asScala
92+
.map(Option.apply)
93+
.toSeq
94+
}
95+
96+
/**
97+
* Provides a view of the DataTable as a simple list of values.
98+
* Equivalent of `.asList()` but returned as Scala collection types without `null` values.
99+
*
100+
* @return list of values
101+
*/
102+
def asScalaList: Seq[Option[String]] = asScalaList[String]
103+
104+
/**
105+
* Provides a view of the DataTable as a full table: a key-value map of row where keys are the first column values
106+
* and each row being itself a key-value map where key is the column name.
107+
*
108+
* @tparam K key type
109+
* @return map of rows
110+
*/
111+
def asScalaRowColumnMap[K](implicit evK: ClassTag[K]): Map[K, Map[String, Option[String]]] = {
112+
table.asMap[K, java.util.Map[String, String]](evK.runtimeClass, classOf[java.util.Map[String, String]])
113+
.asScala
114+
.map { case (k, v) => (k, v.asScala.map(nullToNone).toMap) }
115+
.toMap
116+
}
117+
118+
/**
119+
* Provides a view of the DataTable as a full table: a key-value map of row where keys are the first column values
120+
* and each row being itself a key-value map where key is the column name.
121+
*
122+
* @return map of rows
123+
*/
124+
def asScalaRowColumnMap: Map[String, Map[String, Option[String]]] = asScalaRowColumnMap[String]
125+
126+
/**
127+
* Provides a view of the DataTable as a key-value map of row where keys are the first column values
128+
* and each row being a list of values.
129+
*
130+
* @tparam K key type
131+
* @return map of rows
132+
*/
133+
def asScalaRowMap[K](implicit evK: ClassTag[K]): Map[K, Seq[Option[String]]] = {
134+
table.asMap[K, java.util.List[String]](evK.runtimeClass, classOf[java.util.List[String]])
135+
.asScala
136+
.map { case (k, v) => (k, v.asScala.map(Option.apply).toSeq) }
137+
.toMap
138+
}
139+
140+
/**
141+
* Provides a view of the DataTable as a key-value map of row where keys are the first column values
142+
* and each row being a list of values.
143+
*
144+
* @return map of rows
145+
*/
146+
def asScalaRowMap: Map[String, Seq[Option[String]]] = asScalaRowMap[String]
147+
148+
private def nullToNone[K, V](tuple: (K, V)): (K, Option[V]) = {
149+
val (k, v) = tuple
150+
(k, Option(v))
151+
}
152+
153+
}
154+
155+
}

0 commit comments

Comments
 (0)