Skip to content

Commit b1ac774

Browse files
committed
replaced Row typealias with delegate
1 parent ea26ca1 commit b1ac774

File tree

8 files changed

+95
-49
lines changed

8 files changed

+95
-49
lines changed

src/main/kotlin/kscript/KscriptUtil.kt

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,21 @@ import kotlin.system.exitProcess
66
* @author Holger Brandl
77
*/
88

9+
/**
10+
* Just used interally to prevent [stopIfNot] to quit the process when running in unit-test mode.
11+
* It throw an IllegalArgumentException instead.
12+
*/
13+
internal var isTestMode = false
14+
15+
916
/** Similar to require but without a stacktrace which makes it more suited for CLIs. */
10-
inline fun stopIfNot(value: Boolean, lazyMessage: () -> Any) {
11-
if (!value) {
12-
System.err.println("[ERROR] " + lazyMessage().toString())
13-
exitProcess(1)
14-
}
17+
fun stopIfNot(value: Boolean, lazyMessage: () -> Any) {
18+
if (value) return
19+
20+
val msg = "[ERROR] " + lazyMessage().toString()
21+
22+
if (isTestMode) throw IllegalArgumentException(msg)
23+
24+
System.err.println(msg)
25+
exitProcess(1)
1526
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package kscript.text
2+
3+
/** Declare another extension on print to faciliate printing of more base split lines.*/
4+
5+
fun Sequence<List<String>>.print(separator: String = "\t") = map { Row(it) }.print(separator)

src/main/kotlin/kscript/text/Tables.kt

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,16 @@ import kscript.stopIfNot
2323

2424

2525
/** For sake of readability we refer to a list of strings as a row here. */
26-
typealias Row = List<String>
26+
//typealias Row = List<String>
27+
28+
class Row(val data: List<String>) : List<String> by data {
29+
30+
constructor(vararg data: String) : this(data.asList())
31+
32+
override fun get(index: Int): String = data[index - 1]
33+
34+
override fun toString(): String = data.joinToString("\t")
35+
}
2736

2837
//typealias RowSequence = Sequence<Row>
2938
//class RowSequence(input: Sequence<String>){
@@ -35,20 +44,20 @@ typealias Row = List<String>
3544
* @param separator The used separator character which defaults to tabs.
3645
*/
3746
fun Sequence<String>.split(separator: String = "\t"): Sequence<Row> {
38-
return this.map { it.split(separator) }
47+
return this.map { Row(it.split(separator)) }
3948
}
4049

4150
/** awk-like convenience wrapper around split->map->join->print */
4251
fun Sequence<String>.awk(separator: String = "\t", rule: (Row) -> String) = split(separator).map { rule(it) }.print()
4352

4453

4554
fun Sequence<Row>.map(vararg rules: (Row) -> String): Sequence<Row> {
46-
return map { splitLine -> rules.map { it(splitLine) } }
55+
return map { splitLine -> Row(rules.map { it(splitLine) }) }
4756
}
4857

4958
/** Adds a new column to a row. */
5059
fun Sequence<Row>.add(rule: (Row) -> String): Sequence<Row> {
51-
return map { row -> listOf(*row.toTypedArray(), rule(row)) }
60+
return map { row -> Row(*row.toTypedArray(), rule(row)) }
5261
}
5362

5463

@@ -63,8 +72,7 @@ fun Sequence<Row>.join(separator: String = "\t") = map { it.joinToString(separat
6372
/** Joins rows with the provided `separator` and print them to `stdout`. */
6473
fun Sequence<Row>.print(separator: String = "\t") = join(separator).print()
6574

66-
67-
fun List<Row>.print() = forEach { println(it) }
75+
fun List<Row>.print(separator: String = "\t") = asSequence().print(separator)
6876

6977

7078
//
@@ -97,19 +105,15 @@ fun without(index: Int) = NegSelect(arrayOf(index))
97105
fun without(range: IntRange) = NegSelect(range.toList().toTypedArray())
98106

99107

100-
private fun retainColumn(selectIndex: ColSelect, colIndex: Int): Boolean {
101-
val indexInRange = selectIndex.indices.contains(colIndex)
102-
103-
return if (selectIndex is PosSelect) indexInRange else !indexInRange
104-
}
105-
106108
/**
107109
* Select or remove columns by providing an index-vector. Positive selections are done with [with] and negative selections with [without]. Both methods implement a [builder][https://en.wikipedia.org/wiki/Builder_pattern] to construct more complex selectors.
108110
*/
109111
fun Sequence<Row>.select(vararg colIndices: Int): Sequence<Row> {
110112
val isPositive = colIndices.all { it > 0 }
111-
stopIfNot(isPositive || colIndices.all { it < 0 }) {
112-
" Can not mix positive and negative selections"
113+
val isNegative = colIndices.all { it < 0 }
114+
115+
stopIfNot(isPositive xor isNegative) {
116+
"Can not mix positive and negative selections"
113117
}
114118

115119
val selector = if (isPositive) PosSelect(arrayOf(*colIndices.toTypedArray())) else NegSelect(arrayOf(*colIndices.toTypedArray()))
@@ -121,12 +125,14 @@ fun Sequence<Row>.select(columns: ColSelect): Sequence<Row> {
121125
// more efficient but does not allow to change the order
122126
// return map { it.filterIndexed { index, _ -> retainColumn(columns, index + 1) } }
123127

128+
stopIfNot(columns.indices.all { it != 0 }) { "kscript.text.* is using 1-based arrays to ease awk transition" }
129+
124130
return if (columns is PosSelect) {
125131
// positive selection
126-
map { row -> columns.indices.map { row[it - 1] } }
132+
map { row -> Row(columns.indices.map { row[it] }) }
127133
} else {
128134
// negative selection
129-
map { it.filterIndexed { index, _ -> !columns.indices.contains(index - 1) } }
135+
map { Row(it.filterIndexed { index, _ -> !columns.indices.contains(index) }) }
130136
}
131137
}
132138

src/main/kotlin/kscript/experimental/OneLinerContext.kt renamed to src/main/kotlin/kscript/util/OneLinerContext.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package kscript.experimental
1+
package kscript.util
22

33
import kscript.text.resolveArgFile
44

src/test/kotlin/kscript/examples/AwkComparison.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package kscript.examples
22

3-
import kscript.experimental.OneLinerContext
3+
import kscript.util.OneLinerContext
44
import kscript.text.*
55

66
/**
@@ -29,7 +29,7 @@ object AwkExample : OneLinerContext(args) {
2929
lines.awk { it[3] }
3030

3131
// http://stackoverflow.com/questions/15361632/delete-a-column-with-awk-or-sed
32-
lines.split().map { it.toMutableList().apply { removeAt(3) } }.print()
32+
// lines.split().select((-3).print()
3333

3434

3535

@@ -87,10 +87,14 @@ object AwkExample : OneLinerContext(args) {
8787
// Prints Record(line) number, and number of fields in that record
8888
// awk '{print NR,"->",NF}' file.txt
8989
lines.split().mapIndexed { index, row -> index.toString() + " -> " + row.size }.print()
90+
lines.split().mapIndexed { index, row -> index.toString() + " -> " + row.size }.print()
9091

9192

9293
val arg by lazy { resolveArgFile(args) }
9394
arg.filter { true }.print()
95+
96+
lines.split().select(with(0).and(1)).print()
97+
9498
}
9599
}
96100
//file:///Users/brandl/Desktop/awk_cheatsheets.pdf

src/test/kotlin/kscript/examples/OneLinerExample.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package kscript.examples
22

3-
import kscript.experimental.OneLinerContext
3+
import kscript.util.OneLinerContext
44
import kscript.text.add
55
import kscript.text.print
66
import kscript.text.split

src/test/kotlin/kscript/test/SupportApiTest.kt

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,52 @@
11
package kscript.test
22

3-
import io.kotlintest.matchers.should
4-
import io.kotlintest.matchers.shouldBe
5-
import io.kotlintest.matchers.shouldEqual
6-
import io.kotlintest.matchers.startWith
3+
import io.kotlintest.matchers.*
74
import io.kotlintest.specs.StringSpec
8-
import kscript.text.linesFrom
9-
import kscript.text.print
10-
import kscript.text.resolveArgFile
11-
import kscript.text.split
12-
import java.io.File
5+
import kscript.isTestMode
6+
import kscript.text.*
137

148
/**
159
* @author Holger Brandl
1610
*/
11+
12+
13+
fun someFlights() = resolveArgFile(arrayOf("src/test/resources/some_flights.tsv"))
14+
15+
fun flightsZipped() = resolveArgFile(arrayOf("src/test/resources/flights.tsv.gz"))
16+
fun flights() = resolveArgFile(arrayOf("src/test/resources/flights.txt"))
17+
18+
1719
class SupportApiTest : StringSpec() { init {
1820

19-
// "allow to use stdin for filtering and mapping" {
20-
// kscript.text.stdin.filter { "^de0[-0]*".toRegex().matches(it) }.map { it + "foo:" }.print()
21-
// }
21+
isTestMode = true
22+
2223

2324
"extract field with column filter" {
24-
linesFrom(File("src/test/resources/flights_head.txt")).split().
25-
filter { it[8] == "N14228" }.
26-
map { it[10] }.
25+
someFlights().split().
26+
filter { it[12] == "N14228" }.
27+
map { it[13] }.
2728
toList().
2829
apply {
2930
size shouldEqual 1
3031
first() shouldEqual "EWR"
3132
}
33+
}
34+
35+
36+
"allow to select columsn" {
37+
someFlights().split()
38+
.select(with(3).and(11..13).and(1))
39+
.first() shouldBe listOf("day", "flight", "tailnum", "origin", "year")
40+
}
41+
3242

43+
"rejeced mixed select" {
44+
shouldThrow<IllegalArgumentException> {
45+
someFlights().split().select(1, -2)
46+
}.message shouldBe "[ERROR] Can not mix positive and negative selections"
3347
}
3448

49+
3550
"compressed lines should be unzipped on the fly"{
3651
resolveArgFile(arrayOf("src/test/resources/flights.tsv.gz")).
3752
drop(1).first() should startWith("2013")

src/test/kotlin/kscript/test/TestIncubator.kt

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,36 @@ import kscript.text.*
66
* @author Holger Brandl
77
*/
88

9-
fun flightsHead() = resolveArgFile(arrayOf("flights_head.txt"))
10-
11-
fun flightsZipped() = resolveArgFile(arrayOf("flights.tsv.gz"))
12-
fun flights() = resolveArgFile(arrayOf("flights.txt"))
139

1410
// just used for testing and development
1511
fun linesFrom(vararg lines: String) = lines.asSequence()
1612

1713

1814
fun main(args: Array<String>) {
15+
someFlights().split().map { listOf(it[1], it[2], "F11-" + it[7]) }.print()
16+
someFlights().split().map { Row(it[1], it[2], "F11-" + it[7]) }.print()
17+
// kscript 'lines.split().map { listOf(it[1], it[2], "F11-"+ it[7]) }.print()' some_flights.tsv
18+
// kscript 'lines.split().map { Row(it[1], it[2], "F11-"+ it[7]) }.print()' some_flights.tsv
19+
20+
someFlights().split()
21+
.select(with(3).and(11..13).and(1))
22+
.first().print() //shouldBe listOf("day", "flight", "tailnum", "origin", "year")
23+
1924
// remove header
20-
flightsHead().drop(1).take(5)
25+
someFlights().drop(1).take(5)
2126

2227

2328
// positive selection
2429
System.out.println("positive selection")
2530
// flightsHead().split().select(with(1..3).and(3)).print()
26-
flightsHead().split().select(1, 10, 12).print()
27-
flightsHead().split().select(10, 1, 12).print()
31+
someFlights().split().select(1, 10, 12).print()
32+
someFlights().split().select(10, 1, 12).print()
2833

2934
//create column
3035
//create column
31-
flightsHead().split().map { listOf(it[1], it[2], "F11-" + it[7]) }.print()
36+
someFlights().split().map { listOf(it[1], it[2], "F11-" + it[7]) }.print()
3237

3338
// negative selection
3439
System.out.println("negative selection")
35-
flightsHead().split().select(without(7).and(3..4)).print()
40+
someFlights().split().select(without(7).and(3..4)).print()
3641
}

0 commit comments

Comments
 (0)