Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add cfg command for projects override #29

Merged
merged 11 commits into from
Oct 1, 2024
1 change: 1 addition & 0 deletions docs/p.tree
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
<toc-element topic="pakku-init.md"/>
<toc-element topic="pakku-import.md"/>
<toc-element topic="pakku-set.md"/>
<toc-element topic="pakku-cfg.md"/>
<toc-element topic="pakku-add.md">
<toc-element topic="pakku-add-prj.md"/>
</toc-element>
Expand Down
3 changes: 2 additions & 1 deletion docs/topics/Config-File.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ modpack export.

`projects`
: A list of project slugs, names, IDs or filenames from the [lock file](Lock-File.md)
with properties you want to change.
with properties you want to change.
You can use [cfg](pakku-cfg.md) command to change the properties as well.
50 changes: 50 additions & 0 deletions docs/topics/pakku-cfg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# pakku cfg

Configure various options of your modpack or projects

## Usage

<snippet id="snippet-cmd">

<var name="cmd">cfg</var>
<var name="params">[&lt;options&gt;] [&lt;projects&gt;]...</var>
<include from="_template_cmd.md" element-id="template-cmd"/>

</snippet>

## Arguments

<snippet id="snippet-args">

`[<projects>]...`
: The `projects` argument.

</snippet>

## Options

<snippet id="snippet-options-all">

<snippet id="snippet-options">

`-t`, `--type`
: Change the type of the project.

`-s`, `--side`
: Change the side of the project.

`-u`, `--update-strategy`
: Change the update strategy of the project.

`-r`, `--redistributable`
: Change whether the project can be redistributed.

`-p`, `--subpath`
: Change the subpath of the project.

</snippet>

`-h`, `--help`
: Show this message and exit

</snippet>
8 changes: 4 additions & 4 deletions docs/topics/pakku-set.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Set various properties of your modpack or projects
<snippet id="snippet-args">

`[<projects>]...`
: The `projects` argument
: The `projects` argument. **Deprecated, use [cfg](pakku-cfg.md) command or config file instead**

</snippet>

Expand All @@ -28,13 +28,13 @@ Set various properties of your modpack or projects
<snippet id="snippet-options">

`-s`, `--side`
: Change the side of a project
: Change the side of a project. **Deprecated, use [cfg](pakku-cfg.md) command or config file instead**

`-u`, `--update-strategy`
: Change the update strategy of a project
: Change the update strategy of a project. **Deprecated, use [cfg](pakku-cfg.md) command or config file instead**

`-r`, `--redistributable`
: Change whether the project can be redistributed
: Change whether the project can be redistributed. **Deprecated, use [cfg](pakku-cfg.md) command or config file instead**

`-t`, `--target`
: Change the target of the pack
Expand Down
2 changes: 1 addition & 1 deletion src/commonMain/kotlin/teksturepako/pakku/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ fun main(args: Array<String>)
Pakku().context {
terminal = cliConfig?.toTerminal() ?: Terminal(theme = CliThemes.Default)
}.subcommands(
Init(), Import(), Set(), Add(), Rm(), Status(), Update(), Ls(), Fetch(), Link(), Export(), Diff()
Init(), Import(), Set(), Add(), Rm(), Cfg(), Status(), Update(), Ls(), Fetch(), Link(), Export(), Diff()
).main(args)

// Check Modrinth's rate limit
Expand Down
10 changes: 5 additions & 5 deletions src/commonMain/kotlin/teksturepako/pakku/api/data/ConfigFile.kt
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,11 @@ data class ConfigFile(

@Serializable
data class ProjectConfig(
var type: ProjectType?,
var side: ProjectSide?,
@SerialName("update_strategy") var updateStrategy: UpdateStrategy?,
@SerialName("redistributable") var redistributable: Boolean?,
var subpath: String?
var type: ProjectType? = null,
var side: ProjectSide? = null,
@SerialName("update_strategy") var updateStrategy: UpdateStrategy? = null,
@SerialName("redistributable") var redistributable: Boolean? = null,
var subpath: String? = null
)

// -- FILE I/O --
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ package teksturepako.pakku.api.projects
enum class UpdateStrategy(serialName: String)
{
LATEST("latest"),
SAME_LATEST("same_latest"), // TODO: To be implemented
// SAME_LATEST("same_latest"), TODO: To be implemented
NONE("none")
}
112 changes: 112 additions & 0 deletions src/commonMain/kotlin/teksturepako/pakku/cli/cmd/Cfg.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package teksturepako.pakku.cli.cmd

import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.core.Context
import com.github.ajalt.clikt.core.terminal
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.arguments.multiple
import com.github.ajalt.clikt.parameters.options.*
import com.github.ajalt.clikt.parameters.types.boolean
import com.github.ajalt.clikt.parameters.types.choice
import com.github.ajalt.clikt.parameters.types.enum
import kotlinx.coroutines.runBlocking
import teksturepako.pakku.api.actions.ActionError
import teksturepako.pakku.api.data.ConfigFile
import teksturepako.pakku.api.data.LockFile
import teksturepako.pakku.api.projects.ProjectSide
import teksturepako.pakku.api.projects.ProjectType
import teksturepako.pakku.api.projects.UpdateStrategy
import teksturepako.pakku.cli.ui.pError

class Cfg : CliktCommand()
{
override val printHelpOnEmptyArgs = true

override fun help(context: Context) = "Configure various options of your modpack or projects"

// -- PROJECTS --

private val projectArgs: List<String> by argument("projects", help = "Projects to configure").multiple()

SettingDust marked this conversation as resolved.
Show resolved Hide resolved
private val typeOpt: ProjectType? by option("-t", "--type")
.help("Change the type of a project")
.enum<ProjectType>()

private val sideOpt: ProjectSide? by option("-s", "--side")
.help("Change the side of a project")
.enum<ProjectSide>()

private val updateStrategyOpt: UpdateStrategy? by option("-u", "--update-strategy")
.help("Change the update strategy of a project")
.enum<UpdateStrategy>()

private val redistributableOpt: Boolean? by option("-r", "--redistributable")
.help("Change whether the project can be redistributed")
.boolean()

private val subpathOpt: String? by option("-p", "--subpath")
.help("Change the subpath of the project")

override fun run() = runBlocking {
val lockFile = LockFile.readToResult().getOrElse {
terminal.danger(it.message)
echo()
return@runBlocking
}
val configFile = ConfigFile.readOrNew()

// -- PROJECTS --

if (projectArgs.isNotEmpty())
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need this if expression.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm assuming the name, version and others will be added to this command

{
val projects = configFile.getProjects()

for (arg in projectArgs)
{
val project = lockFile.getProject(arg)
if (project == null)
{
terminal.pError(ActionError.ProjNotFound(), arg)
continue
}
val projectConfig = projects.getOrPut(arg) {
SettingDust marked this conversation as resolved.
Show resolved Hide resolved
ConfigFile.ProjectConfig()
}

projectConfig.apply {
typeOpt?.let {
type = it
terminal.success("'type' set to '$it' for $arg")
}

sideOpt?.let {
side = it
terminal.success("'side' set to '$it' for $arg")
}

updateStrategyOpt?.let {
updateStrategy = it
terminal.success("'update_strategy' set to '$it' for $arg")
}

redistributableOpt?.let { opt ->
redistributable = opt
terminal.success("'redistributable' set to '$opt' for $arg")
}

subpathOpt?.let { opt ->
subpath = opt
terminal.success("'subpath' set to '$opt' for $arg")
}
}
}
}

configFile.write()?.let {
terminal.pError(it, ConfigFile.FILE_NAME)
echo()
return@runBlocking
}
echo()
}
}
29 changes: 16 additions & 13 deletions src/commonMain/kotlin/teksturepako/pakku/cli/cmd/Set.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,39 @@ import com.github.ajalt.clikt.parameters.arguments.multiple
import com.github.ajalt.clikt.parameters.options.*
import com.github.ajalt.clikt.parameters.types.boolean
import com.github.ajalt.clikt.parameters.types.choice
import com.github.ajalt.clikt.parameters.types.enum
import kotlinx.coroutines.runBlocking
import teksturepako.pakku.api.data.LockFile
import teksturepako.pakku.api.projects.ProjectSide
import teksturepako.pakku.api.projects.UpdateStrategy

class Set : CliktCommand()
{
override val printHelpOnEmptyArgs = true

override fun help(context: Context) = "Set various properties of your modpack or projects"

// -- PROJECTS --

private val projectArgs: List<String> by argument("projects").multiple()
private val projectArgs: List<String> by argument(
"projects",
help = "Use the config file (pakku.json) or 'cfg' command instead."
SettingDust marked this conversation as resolved.
Show resolved Hide resolved
).multiple()

private val sideOpt: String? by option("-s", "--side")
private val sideOpt: ProjectSide? by option("-s", "--side")
.help("Change the side of a project")
.choice("client", "server", "both", ignoreCase = true)
.deprecated("Use the config file (pakku.json) instead.")
.enum<ProjectSide>(true)
.deprecated("Use the config file (pakku.json) or 'cfg' command instead.")

private val updateStrategyOpt: String? by option("-u", "--update-strategy")
private val updateStrategyOpt: UpdateStrategy? by option("-u", "--update-strategy")
.help("Change the update strategy of a project")
.choice("latest", "none", ignoreCase = true)
.enum<UpdateStrategy>(true)
.deprecated("Use the config file (pakku.json) or 'cfg' command instead.")

private val redistributableOpt: Boolean? by option("-r", "--redistributable")
.help("Change whether the project can be redistributed")
.boolean()
.deprecated("Use the config file (pakku.json) instead.")
.deprecated("Use the config file (pakku.json) or 'cfg' command instead.")

// -- PACK --

Expand Down Expand Up @@ -65,16 +72,12 @@ class Set : CliktCommand()
val project = lockFile.getProject(arg) ?: continue

project.apply {
sideOpt?.let { opt ->
ProjectSide.valueOf(opt.uppercase())
}?.let {
sideOpt?.let {
side = it
terminal.success("'side' set to '$it' for ${this.slug}")
}

updateStrategyOpt?.let { opt ->
UpdateStrategy.valueOf(opt.uppercase())
}?.let {
updateStrategyOpt?.let {
updateStrategy = it
terminal.success("'update_strategy' set to '$it' for ${this.slug}")
}
Expand Down
66 changes: 66 additions & 0 deletions src/commonTest/kotlin/teksturepako/pakku/cli/cmd/CfgTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package teksturepako.pakku.cli.cmd

import com.github.ajalt.clikt.testing.test
import com.github.michaelbull.result.runCatching
import kotlinx.coroutines.runBlocking
import teksturepako.pakku.api.data.ConfigFile
import teksturepako.pakku.api.data.LockFile
import teksturepako.pakku.api.data.workingPath
import teksturepako.pakku.api.projects.Project
import teksturepako.pakku.api.projects.ProjectSide
import teksturepako.pakku.api.projects.ProjectType
import teksturepako.pakku.api.projects.UpdateStrategy
import kotlin.io.path.*
import kotlin.test.Test
import kotlin.test.assertContains
import kotlin.test.assertEquals
import kotlin.test.assertNotNull

@OptIn(ExperimentalPathApi::class)
class CfgTest
{
init
{
workingPath = "./build/test"
runCatching { Path("./build/test").deleteRecursively() }
runCatching { Path("./build/test").createDirectory() }
}

@Test
fun `should fail without lock file`()
{
val cmd = Cfg()
val output = cmd.test("test -p test").output

assertContains(output, "Can't find project 'test'")
}

@Test
fun `should success with lock file & project`()
{
runBlocking {
val lockFile = LockFile.readOrNew()
lockFile.add(
Project(
type = ProjectType.MOD,
slug = mutableMapOf("modrinth" to "test"),
name = mutableMapOf("modrinth" to "Test"),
id = mutableMapOf("modrinth" to "test"),
files = mutableSetOf()
)
)
lockFile.write()
}

val cmd = Cfg()
val output = cmd.test("test -p test -s both -u latest -r true")
assertEquals("", output.stderr, "Command failed to execute")
assertNotNull(ConfigFile.readOrNull(), "Config file should be created")
val config = ConfigFile.readOrNull()!!.getProjects()["test"]
assertNotNull(config, "Project config should be created")
assertEquals(UpdateStrategy.LATEST, config.updateStrategy)
assertEquals(true, config.redistributable)
assertEquals("test", config.subpath)
assertEquals(ProjectSide.BOTH, config.side)
}
}