Skip to content

Commit 440d7ff

Browse files
Merge pull request #70 from juraj-hrivnak/refactor/glob
Refactor glob pattern matching
2 parents 8c80429 + 7312493 commit 440d7ff

File tree

11 files changed

+233
-84
lines changed

11 files changed

+233
-84
lines changed

src/commonMain/kotlin/teksturepako/pakku/api/actions/export/Export.kt

+66-21
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package teksturepako.pakku.api.actions.export
22

3+
import com.github.michaelbull.result.Result
34
import com.github.michaelbull.result.get
45
import com.github.michaelbull.result.onFailure
56
import com.github.michaelbull.result.runCatching
@@ -46,12 +47,24 @@ suspend fun export(
4647
configFile: ConfigFile,
4748
platforms: List<Platform>
4849
): List<Job> = coroutineScope {
50+
val overrides: Deferred<List<Result<String, ActionError>>> = async {
51+
configFile.getAllOverrides()
52+
}
53+
54+
val serverOverrides: Deferred<List<Result<String, ActionError>>> = async {
55+
configFile.getAllServerOverrides()
56+
}
57+
58+
val clientOverrides: Deferred<List<Result<String, ActionError>>> = async {
59+
configFile.getAllClientOverrides()
60+
}
61+
4962
profiles.map { profile ->
5063
launch {
5164
profile.export(
5265
onError = { profile, error -> onError(profile, error) },
5366
onSuccess = { profile, path, duration -> onSuccess(profile, path, duration) },
54-
lockFile, configFile, platforms
67+
lockFile, configFile, platforms, overrides, serverOverrides, clientOverrides
5568
)
5669
}
5770
}
@@ -62,7 +75,10 @@ suspend fun ExportProfile.export(
6275
onSuccess: suspend (profile: ExportProfile, path: Path, duration: Duration) -> Unit,
6376
lockFile: LockFile,
6477
configFile: ConfigFile,
65-
platforms: List<Platform>
78+
platforms: List<Platform>,
79+
overrides: Deferred<List<Result<String, ActionError>>>,
80+
serverOverrides: Deferred<List<Result<String, ActionError>>>,
81+
clientOverrides: Deferred<List<Result<String, ActionError>>>,
6682
)
6783
{
6884
if (this.dependsOn != null && this.dependsOn !in platforms) return
@@ -88,8 +104,10 @@ suspend fun ExportProfile.export(
88104

89105
val inputDirectory = Path(cacheDir.pathString, this.name)
90106

91-
val results: List<RuleResult> = this.rules.filterNotNull()
92-
.produceRuleResults({ onError(this, it) }, lockFile, configFile, this.name)
107+
val results: List<RuleResult> = this.rules.filterNotNull().produceRuleResults(
108+
onError = { onError(this, it) },
109+
lockFile, configFile, this.name, overrides, serverOverrides, clientOverrides
110+
)
93111

94112
// Run export rules
95113
val cachedPaths: List<Path> = results.resolveResults { onError(this, it) }.awaitAll().filterNotNull() +
@@ -281,44 +299,71 @@ suspend fun List<RuleResult>.finishResults(
281299
*/
282300
suspend fun List<ExportRule>.produceRuleResults(
283301
onError: suspend (error: ActionError) -> Unit,
284-
lockFile: LockFile, configFile: ConfigFile, workingSubDir: String
285-
): List<RuleResult>
286-
{
287-
val results = this.fold(listOf<Pair<ExportRule, RuleContext>>()) { acc, rule ->
288-
acc + lockFile.getAllProjects().map {
289-
rule to RuleContext.ExportingProject(it, lockFile, configFile, workingSubDir)
290-
} + configFile.getAllOverrides().mapNotNull { result ->
302+
lockFile: LockFile, configFile: ConfigFile, workingSubDir: String,
303+
overrides: Deferred<List<Result<String, ActionError>>>,
304+
serverOverrides: Deferred<List<Result<String, ActionError>>>,
305+
clientOverrides: Deferred<List<Result<String, ActionError>>>,
306+
): List<RuleResult> = coroutineScope {
307+
val projects: Deferred<List<RuleContext.ExportingProject>> = async {
308+
lockFile.getAllProjects().map {
309+
RuleContext.ExportingProject(it, lockFile, configFile, workingSubDir)
310+
}
311+
}
312+
313+
val overridesR: Deferred<List<RuleContext.ExportingOverride>> = async {
314+
overrides.await().mapNotNull { result ->
291315
result.resultFold(
292316
success = {
293-
rule to RuleContext.ExportingOverride(it, OverrideType.OVERRIDE, lockFile, configFile, workingSubDir)
317+
RuleContext.ExportingOverride(it, OverrideType.OVERRIDE, lockFile, configFile, workingSubDir)
294318
},
295319
failure = {
296320
onError(it)
297321
null
298322
}
299323
)
300-
} + configFile.getAllServerOverrides().mapNotNull { result ->
324+
}
325+
}
326+
327+
val serverOverridesR: Deferred<List<RuleContext.ExportingOverride>> = async {
328+
serverOverrides.await().mapNotNull { result ->
301329
result.resultFold(
302330
success = {
303-
rule to RuleContext.ExportingOverride(it, OverrideType.SERVER_OVERRIDE, lockFile, configFile, workingSubDir)
331+
RuleContext.ExportingOverride(it, OverrideType.SERVER_OVERRIDE, lockFile, configFile, workingSubDir)
304332
},
305333
failure = {
306334
onError(it)
307335
null
308336
}
309337
)
310-
} + configFile.getAllClientOverrides().mapNotNull { result ->
338+
}
339+
}
340+
341+
val clientOverridesR: Deferred<List<RuleContext.ExportingOverride>> = async {
342+
clientOverrides.await().mapNotNull { result ->
311343
result.resultFold(
312344
success = {
313-
rule to RuleContext.ExportingOverride(it, OverrideType.CLIENT_OVERRIDE, lockFile, configFile, workingSubDir)
345+
RuleContext.ExportingOverride(it, OverrideType.CLIENT_OVERRIDE, lockFile, configFile, workingSubDir)
314346
},
315347
failure = {
316348
onError(it)
317349
null
318350
}
319351
)
320-
} + readProjectOverrides(configFile).map {
321-
rule to RuleContext.ExportingProjectOverride(it, lockFile, configFile, workingSubDir)
352+
}
353+
}
354+
355+
val projectOverrides: Deferred<List<RuleContext.ExportingProjectOverride>> = async {
356+
readProjectOverrides(configFile).map {
357+
RuleContext.ExportingProjectOverride(it, lockFile, configFile, workingSubDir)
358+
}
359+
}
360+
361+
val deferredContexts = listOf(projects, overridesR, serverOverridesR, clientOverridesR, projectOverrides)
362+
val contexts = deferredContexts.awaitAll().flatten()
363+
364+
val results = this@produceRuleResults.fold(listOf<Pair<ExportRule, RuleContext>>()) { acc, rule ->
365+
acc + contexts.map { context ->
366+
rule to context
322367
}
323368
}.map { (exportRule, ruleContext) ->
324369
exportRule.getResult(ruleContext)
@@ -327,15 +372,15 @@ suspend fun List<ExportRule>.produceRuleResults(
327372
val missing = results.filter {
328373
it.ruleContext is RuleContext.MissingProject
329374
}.flatMap { ruleResult ->
330-
this.map { rule ->
375+
this@produceRuleResults.map { rule ->
331376
val project = (ruleResult.ruleContext as RuleContext.MissingProject).project
332377
rule.getResult(RuleContext.MissingProject(project, lockFile, configFile, workingSubDir))
333378
}
334379
}
335380

336-
val finished = this.map { rule ->
381+
val finished = this@produceRuleResults.map { rule ->
337382
rule.getResult(Finished(lockFile, configFile, workingSubDir))
338383
}
339384

340-
return results + missing + finished
385+
return@coroutineScope results + missing + finished
341386
}

src/commonMain/kotlin/teksturepako/pakku/api/data/ConfigFile.kt

+6-6
Original file line numberDiff line numberDiff line change
@@ -101,14 +101,14 @@ data class ConfigFile(
101101
this.overrides.clear()
102102
}
103103

104-
fun getAllOverrides(): List<Result<String, ActionError>> = this.overrides
105-
.map { filterPath(it) }
104+
suspend fun getAllOverrides(): List<Result<String, ActionError>> =
105+
this.overrides.expandWithGlob(Path(workingPath)).map { filterPath(it) }
106106

107-
fun getAllServerOverrides(): List<Result<String, ActionError>> = this.serverOverrides
108-
.map { filterPath(it) }
107+
suspend fun getAllServerOverrides(): List<Result<String, ActionError>> =
108+
this.serverOverrides.expandWithGlob(Path(workingPath)).map { filterPath(it) }
109109

110-
fun getAllClientOverrides(): List<Result<String, ActionError>> = this.clientOverrides
111-
.map { filterPath(it) }
110+
suspend fun getAllClientOverrides(): List<Result<String, ActionError>> =
111+
this.clientOverrides.expandWithGlob(Path(workingPath)).map { filterPath(it) }
112112

113113
// -- PROJECTS --
114114

Original file line numberDiff line numberDiff line change
@@ -1,29 +1,63 @@
11
package teksturepako.pakku.io
22

3+
import com.github.michaelbull.result.get
4+
import com.github.michaelbull.result.runCatching
5+
import kotlinx.coroutines.Dispatchers
6+
import kotlinx.coroutines.withContext
7+
import teksturepako.pakku.debug
38
import teksturepako.pakku.debugIf
49
import java.io.File
510
import java.nio.file.FileSystems
611
import java.nio.file.Path
12+
import java.nio.file.PathMatcher
713
import kotlin.io.path.*
814

915
@OptIn(ExperimentalPathApi::class)
10-
fun Path.listDirectoryEntriesRecursive(glob: String): Sequence<Path>
11-
{
12-
val globPattern = "glob:${this.invariantSeparatorsPathString}/${glob.removePrefix("./")}"
13-
val matcher = FileSystems.getDefault().getPathMatcher(globPattern)
16+
suspend fun Path.walk(globPatterns: List<String>): Sequence<Pair<Path, Boolean>> = withContext(Dispatchers.IO) {
17+
val matchers: List<Pair<PathMatcher, Boolean>> = globPatterns.mapNotNull { input ->
18+
val negating = input.startsWith("!")
19+
val glob = (if (negating) input.removePrefix("!") else input).removePrefix("./")
20+
21+
val globPattern = "glob:${this@walk.invariantSeparatorsPathString}/$glob"
22+
runCatching { FileSystems.getDefault().getPathMatcher(globPattern) to negating }.get()
23+
}
24+
25+
return@withContext this@walk.walk(PathWalkOption.INCLUDE_DIRECTORIES)
26+
.mapNotNull { path ->
27+
val matcher = matchers.filter { (matcher) -> matcher.matches(path) }
1428

15-
return this.walk(PathWalkOption.INCLUDE_DIRECTORIES)
16-
.filter { matcher.matches(it) }
17-
.map { Path(it.absolutePathString().removePrefix(this.absolutePathString()).removePrefix(File.separator)) }
29+
if (matcher.isEmpty()) return@mapNotNull null
30+
31+
matcher to path
32+
}.map { (matchers, path) ->
33+
val resultPath = Path(path.absolutePathString().removePrefix(this@walk.absolutePathString()).removePrefix(File.separator))
34+
val negating = matchers.any { it.second }
35+
36+
resultPath to negating
37+
}
1838
}
1939

20-
fun List<String>.expandWithGlob(inputPath: Path) = fold(listOf<String>()) { acc, glob ->
21-
if (glob.startsWith("!"))
40+
suspend fun List<String>.expandWithGlob(inputPath: Path): List<String>
41+
{
42+
val paths = mutableSetOf<String>()
43+
44+
val walk = inputPath.walk(this).toList().debug {
45+
for ((path, negating) in it)
46+
{
47+
println("${if (negating) "!" else ""}$path")
48+
}
49+
}
50+
51+
for ((path, _) in walk)
2252
{
23-
acc - inputPath.listDirectoryEntriesRecursive(glob.removePrefix("!")).map { it.toString() }.toSet()
53+
paths += path.toString()
2454
}
25-
else
55+
56+
for ((path, negating) in walk)
2657
{
27-
acc + inputPath.listDirectoryEntriesRecursive(glob).map { it.toString() }
58+
if (negating) paths -= path.toString()
2859
}
29-
}.debugIf({ it.isNotEmpty() }) { println("expandWithGlob = $it") }
60+
61+
return paths.toList().debugIf({ it.isNotEmpty() }) { println("expandWithGlob = $it") }
62+
}
63+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package teksturepako.pakku
2+
3+
import kotlinx.coroutines.runBlocking
4+
import teksturepako.pakku.api.data.generatePakkuId
5+
import teksturepako.pakku.api.data.workingPath
6+
import kotlin.io.path.*
7+
import kotlin.test.AfterTest
8+
import kotlin.test.BeforeTest
9+
10+
open class PakkuTest
11+
{
12+
private var testName: String = ""
13+
14+
protected open suspend fun `on-set-up`()
15+
{
16+
}
17+
18+
protected open val teardown = true
19+
20+
protected fun createTestFile(vararg path: String)
21+
{
22+
val file = Path(workingPath, *path)
23+
runCatching { file.createParentDirectories() }
24+
runCatching { file.createFile() }
25+
}
26+
27+
protected fun createTestDir(vararg path: String)
28+
{
29+
val dir = Path(workingPath, *path)
30+
runCatching { dir.createParentDirectories() }
31+
runCatching { dir.createDirectory() }
32+
}
33+
34+
@BeforeTest
35+
fun `set-up-test`()
36+
{
37+
testName = this::class.simpleName ?: generatePakkuId()
38+
39+
println("Setting up test: $testName")
40+
41+
workingPath = "./build/test/$testName"
42+
runCatching { Path("./build/test/$testName").createParentDirectories() }
43+
runCatching { Path("./build/test/$testName").createDirectory() }
44+
45+
runBlocking { this@PakkuTest.`on-set-up`() }
46+
}
47+
48+
@AfterTest
49+
@OptIn(ExperimentalPathApi::class)
50+
fun `tear-down-test`()
51+
{
52+
if (!teardown) return
53+
54+
workingPath = "."
55+
56+
println("Tearing down test: $testName")
57+
58+
runCatching { Path("./build/test/$testName").deleteRecursively() }
59+
}
60+
}

src/commonTest/kotlin/teksturepako/pakku/api/actions/export/TestExport.kt

-6
This file was deleted.

src/commonTest/kotlin/teksturepako/pakku/api/platforms/CurseForgeTest.kt

-5
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,6 @@ import kotlin.test.assertEquals
1111

1212
class CurseForgeTest
1313
{
14-
@Test
15-
fun requestProject()
16-
{
17-
}
18-
1914
@Test
2015
fun sortByLoaders_WithValidLoaders_ShouldCompareCorrectly() {
2116
val files = listOf(

src/commonTest/kotlin/teksturepako/pakku/api/projects/ProjectTest.kt

+3-3
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,21 @@ class ProjectTest
1010
@Test
1111
fun hasAliasOf_whenProjectHasAlias_returnsTrue()
1212
{
13-
val project1 = mockk<Project>() {
13+
val project1 = mockk<Project> {
1414
every { id } returns mutableMapOf("id1" to "id1")
1515
every { name } returns mutableMapOf("name1" to "name1")
1616
every { slug } returns mutableMapOf("slug1" to "slug1")
1717
every { aliases } returns mutableSetOf("id3")
1818
every { hasAliasOf(any()) } answers { callOriginal() }
1919
}
20-
val project2 = mockk<Project>() {
20+
val project2 = mockk<Project> {
2121
every { id } returns mutableMapOf("id2" to "id2")
2222
every { name } returns mutableMapOf("name2" to "name2")
2323
every { slug } returns mutableMapOf("slug2" to "slug2")
2424
every { aliases } returns mutableSetOf("name1")
2525
every { hasAliasOf(any()) } answers { callOriginal() }
2626
}
27-
val project3 = mockk<Project>() {
27+
val project3 = mockk<Project> {
2828
every { id } returns mutableMapOf("id3" to "id3")
2929
every { name } returns mutableMapOf("name3" to "name3")
3030
every { slug } returns mutableMapOf("slug3" to "slug3")

0 commit comments

Comments
 (0)