Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
juraj-hrivnak committed Dec 17, 2024
2 parents 2122db2 + c5b6c3d commit fae29ac
Show file tree
Hide file tree
Showing 14 changed files with 254 additions and 89 deletions.
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,26 @@

# Changelog

## Unreleased

## v0.22.0

### Highlights

- When removing a project, Pakku will always ask whether you want to remove each of its dependencies.
- Added glob pattern support for specifying overrides, server_overrides and client_overrides, in [#70](https://github.com/juraj-hrivnak/Pakku/pull/70).
- This new implementation is optimized to work with large modpacks.
- For more info see [v0.20.0](https://github.com/juraj-hrivnak/Pakku/releases/tag/v0.20.0).

### Fixes

- Removed hyperlink from exported modpack file path messages. They didn't work properly on all platforms.

### API

- Breaking change: Refactored action errors to separate data classes.
- Created a mini framework for simplifying tests.

## v0.21.0

- Improved projects updating, by @SettingDust in [#49](https://github.com/juraj-hrivnak/Pakku/pull/49).
Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ plugins {
}

group = "teksturepako.pakku"
version = "0.21.0"
version = "0.22.0"

val nativeEnabled = false

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package teksturepako.pakku.api.actions.export

import com.github.michaelbull.result.Result
import com.github.michaelbull.result.get
import com.github.michaelbull.result.onFailure
import com.github.michaelbull.result.runCatching
Expand Down Expand Up @@ -46,12 +47,24 @@ suspend fun export(
configFile: ConfigFile,
platforms: List<Platform>
): List<Job> = coroutineScope {
val overrides: Deferred<List<Result<String, ActionError>>> = async {
configFile.getAllOverrides()
}

val serverOverrides: Deferred<List<Result<String, ActionError>>> = async {
configFile.getAllServerOverrides()
}

val clientOverrides: Deferred<List<Result<String, ActionError>>> = async {
configFile.getAllClientOverrides()
}

profiles.map { profile ->
launch {
profile.export(
onError = { profile, error -> onError(profile, error) },
onSuccess = { profile, path, duration -> onSuccess(profile, path, duration) },
lockFile, configFile, platforms
lockFile, configFile, platforms, overrides, serverOverrides, clientOverrides
)
}
}
Expand All @@ -62,7 +75,10 @@ suspend fun ExportProfile.export(
onSuccess: suspend (profile: ExportProfile, path: Path, duration: Duration) -> Unit,
lockFile: LockFile,
configFile: ConfigFile,
platforms: List<Platform>
platforms: List<Platform>,
overrides: Deferred<List<Result<String, ActionError>>>,
serverOverrides: Deferred<List<Result<String, ActionError>>>,
clientOverrides: Deferred<List<Result<String, ActionError>>>,
)
{
if (this.dependsOn != null && this.dependsOn !in platforms) return
Expand All @@ -88,8 +104,10 @@ suspend fun ExportProfile.export(

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

val results: List<RuleResult> = this.rules.filterNotNull()
.produceRuleResults({ onError(this, it) }, lockFile, configFile, this.name)
val results: List<RuleResult> = this.rules.filterNotNull().produceRuleResults(
onError = { onError(this, it) },
lockFile, configFile, this.name, overrides, serverOverrides, clientOverrides
)

// Run export rules
val cachedPaths: List<Path> = results.resolveResults { onError(this, it) }.awaitAll().filterNotNull() +
Expand Down Expand Up @@ -281,44 +299,71 @@ suspend fun List<RuleResult>.finishResults(
*/
suspend fun List<ExportRule>.produceRuleResults(
onError: suspend (error: ActionError) -> Unit,
lockFile: LockFile, configFile: ConfigFile, workingSubDir: String
): List<RuleResult>
{
val results = this.fold(listOf<Pair<ExportRule, RuleContext>>()) { acc, rule ->
acc + lockFile.getAllProjects().map {
rule to RuleContext.ExportingProject(it, lockFile, configFile, workingSubDir)
} + configFile.getAllOverrides().mapNotNull { result ->
lockFile: LockFile, configFile: ConfigFile, workingSubDir: String,
overrides: Deferred<List<Result<String, ActionError>>>,
serverOverrides: Deferred<List<Result<String, ActionError>>>,
clientOverrides: Deferred<List<Result<String, ActionError>>>,
): List<RuleResult> = coroutineScope {
val projects: Deferred<List<RuleContext.ExportingProject>> = async {
lockFile.getAllProjects().map {
RuleContext.ExportingProject(it, lockFile, configFile, workingSubDir)
}
}

val overridesR: Deferred<List<RuleContext.ExportingOverride>> = async {
overrides.await().mapNotNull { result ->
result.resultFold(
success = {
rule to RuleContext.ExportingOverride(it, OverrideType.OVERRIDE, lockFile, configFile, workingSubDir)
RuleContext.ExportingOverride(it, OverrideType.OVERRIDE, lockFile, configFile, workingSubDir)
},
failure = {
onError(it)
null
}
)
} + configFile.getAllServerOverrides().mapNotNull { result ->
}
}

val serverOverridesR: Deferred<List<RuleContext.ExportingOverride>> = async {
serverOverrides.await().mapNotNull { result ->
result.resultFold(
success = {
rule to RuleContext.ExportingOverride(it, OverrideType.SERVER_OVERRIDE, lockFile, configFile, workingSubDir)
RuleContext.ExportingOverride(it, OverrideType.SERVER_OVERRIDE, lockFile, configFile, workingSubDir)
},
failure = {
onError(it)
null
}
)
} + configFile.getAllClientOverrides().mapNotNull { result ->
}
}

val clientOverridesR: Deferred<List<RuleContext.ExportingOverride>> = async {
clientOverrides.await().mapNotNull { result ->
result.resultFold(
success = {
rule to RuleContext.ExportingOverride(it, OverrideType.CLIENT_OVERRIDE, lockFile, configFile, workingSubDir)
RuleContext.ExportingOverride(it, OverrideType.CLIENT_OVERRIDE, lockFile, configFile, workingSubDir)
},
failure = {
onError(it)
null
}
)
} + readProjectOverrides(configFile).map {
rule to RuleContext.ExportingProjectOverride(it, lockFile, configFile, workingSubDir)
}
}

val projectOverrides: Deferred<List<RuleContext.ExportingProjectOverride>> = async {
readProjectOverrides(configFile).map {
RuleContext.ExportingProjectOverride(it, lockFile, configFile, workingSubDir)
}
}

val deferredContexts = listOf(projects, overridesR, serverOverridesR, clientOverridesR, projectOverrides)
val contexts = deferredContexts.awaitAll().flatten()

val results = this@produceRuleResults.fold(listOf<Pair<ExportRule, RuleContext>>()) { acc, rule ->
acc + contexts.map { context ->
rule to context
}
}.map { (exportRule, ruleContext) ->
exportRule.getResult(ruleContext)
Expand All @@ -327,15 +372,15 @@ suspend fun List<ExportRule>.produceRuleResults(
val missing = results.filter {
it.ruleContext is RuleContext.MissingProject
}.flatMap { ruleResult ->
this.map { rule ->
this@produceRuleResults.map { rule ->
val project = (ruleResult.ruleContext as RuleContext.MissingProject).project
rule.getResult(RuleContext.MissingProject(project, lockFile, configFile, workingSubDir))
}
}

val finished = this.map { rule ->
val finished = this@produceRuleResults.map { rule ->
rule.getResult(Finished(lockFile, configFile, workingSubDir))
}

return results + missing + finished
return@coroutineScope results + missing + finished
}
12 changes: 6 additions & 6 deletions src/commonMain/kotlin/teksturepako/pakku/api/data/ConfigFile.kt
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,14 @@ data class ConfigFile(
this.overrides.clear()
}

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

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

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

// -- PROJECTS --

Expand Down
5 changes: 1 addition & 4 deletions src/commonMain/kotlin/teksturepako/pakku/cli/cmd/Export.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ import teksturepako.pakku.cli.ui.pError
import teksturepako.pakku.cli.ui.pSuccess
import teksturepako.pakku.cli.ui.shortForm
import teksturepako.pakku.io.toHumanReadableSize
import teksturepako.pakku.io.tryOrNull
import kotlin.io.path.absolutePathString
import kotlin.io.path.fileSize

class Export : CliktCommand()
Expand Down Expand Up @@ -60,9 +58,8 @@ class Export : CliktCommand()
},
onSuccess = { profile, file, duration ->
val fileSize = file.fileSize().toHumanReadableSize()
val filePath = file.tryOrNull { it.absolutePathString() } ?: file.toString()

terminal.pSuccess("[${profile.name} profile] exported to '$filePath' ($fileSize) in ${duration.shortForm()}")
terminal.pSuccess("[${profile.name} profile] exported to '$file' ($fileSize) in ${duration.shortForm()}")
},
lockFile, configFile, platforms
).joinAll()
Expand Down
60 changes: 47 additions & 13 deletions src/commonMain/kotlin/teksturepako/pakku/io/Glob.kt
Original file line number Diff line number Diff line change
@@ -1,29 +1,63 @@
package teksturepako.pakku.io

import com.github.michaelbull.result.get
import com.github.michaelbull.result.runCatching
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import teksturepako.pakku.debug
import teksturepako.pakku.debugIf
import java.io.File
import java.nio.file.FileSystems
import java.nio.file.Path
import java.nio.file.PathMatcher
import kotlin.io.path.*

@OptIn(ExperimentalPathApi::class)
fun Path.listDirectoryEntriesRecursive(glob: String): Sequence<Path>
{
val globPattern = "glob:${this.invariantSeparatorsPathString}/${glob.removePrefix("./")}"
val matcher = FileSystems.getDefault().getPathMatcher(globPattern)
suspend fun Path.walk(globPatterns: List<String>): Sequence<Pair<Path, Boolean>> = withContext(Dispatchers.IO) {
val matchers: List<Pair<PathMatcher, Boolean>> = globPatterns.mapNotNull { input ->
val negating = input.startsWith("!")
val glob = (if (negating) input.removePrefix("!") else input).removePrefix("./")

val globPattern = "glob:${this@walk.invariantSeparatorsPathString}/$glob"
runCatching { FileSystems.getDefault().getPathMatcher(globPattern) to negating }.get()
}

return@withContext this@walk.walk(PathWalkOption.INCLUDE_DIRECTORIES)
.mapNotNull { path ->
val matcher = matchers.filter { (matcher) -> matcher.matches(path) }

return this.walk(PathWalkOption.INCLUDE_DIRECTORIES)
.filter { matcher.matches(it) }
.map { Path(it.absolutePathString().removePrefix(this.absolutePathString()).removePrefix(File.separator)) }
if (matcher.isEmpty()) return@mapNotNull null

matcher to path
}.map { (matchers, path) ->
val resultPath = Path(path.absolutePathString().removePrefix(this@walk.absolutePathString()).removePrefix(File.separator))
val negating = matchers.any { it.second }

resultPath to negating
}
}

fun List<String>.expandWithGlob(inputPath: Path) = fold(listOf<String>()) { acc, glob ->
if (glob.startsWith("!"))
suspend fun List<String>.expandWithGlob(inputPath: Path): List<String>
{
val paths = mutableSetOf<String>()

val walk = inputPath.walk(this).toList().debug {
for ((path, negating) in it)
{
println("${if (negating) "!" else ""}$path")
}
}

for ((path, _) in walk)
{
acc - inputPath.listDirectoryEntriesRecursive(glob.removePrefix("!")).map { it.toString() }.toSet()
paths += path.toString()
}
else

for ((path, negating) in walk)
{
acc + inputPath.listDirectoryEntriesRecursive(glob).map { it.toString() }
if (negating) paths -= path.toString()
}
}.debugIf({ it.isNotEmpty() }) { println("expandWithGlob = $it") }

return paths.toList().debugIf({ it.isNotEmpty() }) { println("expandWithGlob = $it") }
}

60 changes: 60 additions & 0 deletions src/commonTest/kotlin/teksturepako/pakku/PakkuTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package teksturepako.pakku

import kotlinx.coroutines.runBlocking
import teksturepako.pakku.api.data.generatePakkuId
import teksturepako.pakku.api.data.workingPath
import kotlin.io.path.*
import kotlin.test.AfterTest
import kotlin.test.BeforeTest

open class PakkuTest
{
private var testName: String = ""

protected open suspend fun `on-set-up`()
{
}

protected open val teardown = true

protected fun createTestFile(vararg path: String)
{
val file = Path(workingPath, *path)
runCatching { file.createParentDirectories() }
runCatching { file.createFile() }
}

protected fun createTestDir(vararg path: String)
{
val dir = Path(workingPath, *path)
runCatching { dir.createParentDirectories() }
runCatching { dir.createDirectory() }
}

@BeforeTest
fun `set-up-test`()
{
testName = this::class.simpleName ?: generatePakkuId()

println("Setting up test: $testName")

workingPath = "./build/test/$testName"
runCatching { Path("./build/test/$testName").createParentDirectories() }
runCatching { Path("./build/test/$testName").createDirectory() }

runBlocking { this@PakkuTest.`on-set-up`() }
}

@AfterTest
@OptIn(ExperimentalPathApi::class)
fun `tear-down-test`()
{
if (!teardown) return

workingPath = "."

println("Tearing down test: $testName")

runCatching { Path("./build/test/$testName").deleteRecursively() }
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,6 @@ import kotlin.test.assertEquals

class CurseForgeTest
{
@Test
fun requestProject()
{
}

@Test
fun sortByLoaders_WithValidLoaders_ShouldCompareCorrectly() {
val files = listOf(
Expand Down
Loading

0 comments on commit fae29ac

Please sign in to comment.