Skip to content

Commit 27953ce

Browse files
committed
add curated deps step to grill generate
1 parent 6ef755f commit 27953ce

5 files changed

Lines changed: 155 additions & 4 deletions

File tree

src/main/kotlin/file/CLICommand.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package file
22

33
import config.ScriptMode
4+
import logging.KotlinLogging
45

56
enum class CLICommand {
67
HELP,
@@ -83,7 +84,25 @@ enum class GlobalOptions(val optionName: String = "", val argCount: Int = 0) {
8384
setupMain.gamePath = java.nio.file.Paths.get(args[0])
8485
setupMain.gamePathExplicit = true
8586
}
87+
},
88+
WITH_DEP("--with-dep", 1) {
89+
override fun runOption(setupMain: SetupMain, args: List<String>) {
90+
val requested = args[0].trim()
91+
val curated = CuratedDependencies.findById(requested)
92+
if (curated == null) {
93+
log.error("❌ Unknown curated dependency: $requested")
94+
log.info("Available: ${CuratedDependencies.ids.joinToString(", ")}")
95+
ExitHandler.exit(1)
96+
}
97+
if (!setupMain.curatedDependencyIds.contains(curated.id)) {
98+
setupMain.curatedDependencyIds.add(curated.id)
99+
}
100+
}
86101
};
87102

88103
abstract fun runOption(setupMain: SetupMain, args: List<String>)
104+
105+
companion object {
106+
private val log = KotlinLogging.logger {}
107+
}
89108
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package file
2+
3+
/**
4+
* A community library that is commonly useful but is not part of the Wurst
5+
* standard library. The `generate` wizard surfaces these as opt-in extras so a
6+
* fresh project can pick them up front instead of running `grill install <url>`
7+
* afterwards.
8+
*/
9+
data class CuratedDependency(
10+
/** Short, stable handle used by the `--with-dep <id>` flag. */
11+
val id: String,
12+
/** Repository name shown to the user, e.g. "wurst-table-layout". */
13+
val label: String,
14+
/** One-line blurb shown next to the label, e.g. "AI ready UI toolkit". */
15+
val description: String,
16+
/** Dependency URL written into the wurst.build `dependencies` array. */
17+
val url: String
18+
) {
19+
/** e.g. "wurst-table-layout (AI ready UI toolkit)" */
20+
val summary: String get() = "$label ($description)"
21+
}
22+
23+
/**
24+
* The curated catalogue offered by the generate wizard. Add an entry here to
25+
* surface a new optional dependency; both the wizard step and the
26+
* `--with-dep <id>` flag pick it up automatically.
27+
*/
28+
object CuratedDependencies {
29+
val all: List<CuratedDependency> = listOf(
30+
CuratedDependency(
31+
id = "table-layout",
32+
label = "wurst-table-layout",
33+
description = "AI ready UI toolkit",
34+
url = "https://github.com/Frotty/wurst-table-layout"
35+
)
36+
)
37+
38+
fun findById(id: String): CuratedDependency? =
39+
all.firstOrNull { it.id.equals(id, ignoreCase = true) }
40+
41+
/** Curated entries whose URL is present in the given dependency list. */
42+
fun matching(dependencies: List<String>): List<CuratedDependency> =
43+
all.filter { dependency -> dependencies.any { it.equals(dependency.url, ignoreCase = true) } }
44+
45+
val ids: List<String> get() = all.map { it.id }
46+
}

src/main/kotlin/file/SetupApp.kt

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ object SetupApp {
146146
| --wc3-path <dir> Warcraft III install folder for VS Code/run
147147
| --with-agents / --no-agents Include AGENTS.md (default: no)
148148
| --with-ci / --no-ci Include GitHub Actions workflow (default: no)
149+
| --with-dep <id> Add a curated dependency (repeatable; ids: ${CuratedDependencies.ids.joinToString(", ")})
149150
""".trimMargin())
150151
}
151152
setup.command == CLICommand.INSTALL -> {
@@ -191,9 +192,10 @@ object SetupApp {
191192
val projectDir = DEFAULT_DIR.resolve(setup.commandArg)
192193
val projectName = projectDir.fileName?.toString() ?: setup.commandArg
193194
val stdlibUrl = stdlibDependencyForPatch(setup.wc3Patch)
195+
val curatedUrls = setup.curatedDependencyIds.mapNotNull { CuratedDependencies.findById(it)?.url }
194196
val projectConfig = newProjectConfig(
195197
projectName = projectName,
196-
dependencies = listOf(stdlibUrl),
198+
dependencies = (listOf(stdlibUrl) + curatedUrls).distinct(),
197199
buildMapData = generatedBuildMapData(projectName),
198200
scriptMode = setup.scriptMode,
199201
wc3Patch = setup.wc3Patch
@@ -307,6 +309,8 @@ object SetupApp {
307309
addGithubWorkflow: Boolean,
308310
gameRoot: Path?
309311
) {
312+
val curated = CuratedDependencies.matching(projectConfig.dependencies)
313+
val curatedSummary = if (curated.isEmpty()) "none" else curated.joinToString(", ") { it.label }
310314
log.info("""
311315
|✅ Created ${projectDir.fileName}
312316
|
@@ -315,6 +319,7 @@ object SetupApp {
315319
| WC3 patch: ${CoreJassProvider.describePatch(projectConfig.wc3Patch ?: CoreJassProvider.DEFAULT_PATCH)}
316320
| Warcraft III: ${gameRoot?.toAbsolutePath()?.normalize() ?: "not configured"}
317321
| Stdlib: ${if (projectConfig.dependencies.any { it.endsWith(":pre1.29") }) "pre1.29" else "current"}
322+
| Curated dependencies: $curatedSummary
318323
| AGENTS.md: ${yesNo(addAgents)}
319324
| GitHub Actions CI: ${yesNo(addGithubWorkflow)}
320325
|
@@ -477,6 +482,31 @@ object SetupApp {
477482
val ciDefault = if (setup.addGithubWorkflow) "Y" else "N"
478483
val ciInput = prompt("Add GitHub Actions CI?", ciDefault)
479484
setup.addGithubWorkflow = ciInput?.lowercase() == "y"
485+
486+
setup.curatedDependencyIds = selectCuratedDependencies(prompt, setup.curatedDependencyIds).toMutableList()
487+
}
488+
489+
private fun selectCuratedDependencies(
490+
prompt: (String, String?) -> String?,
491+
preselectedIds: List<String>
492+
): List<String> {
493+
val catalog = CuratedDependencies.all
494+
if (catalog.isEmpty()) {
495+
return preselectedIds
496+
}
497+
498+
log.info("Curated dependencies (optional extras beyond the standard library):")
499+
val selected = LinkedHashSet(preselectedIds)
500+
for (dependency in catalog) {
501+
val default = if (selected.contains(dependency.id)) "Y" else "N"
502+
val answer = prompt("Add ${dependency.summary}?", default)
503+
if (answer?.trim()?.lowercase() == "y") {
504+
selected.add(dependency.id)
505+
} else {
506+
selected.remove(dependency.id)
507+
}
508+
}
509+
return selected.toList()
480510
}
481511

482512
private fun selectGamePath(

src/main/kotlin/file/SetupMain.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ class SetupMain {
3333
var scriptMode: ScriptMode = ScriptMode.LUA
3434
var wc3Patch: String = CoreJassProvider.DEFAULT_PATCH
3535

36+
/** Ids of curated dependencies (see [CuratedDependencies]) to seed into the generated project. */
37+
var curatedDependencyIds: MutableList<String> = mutableListOf()
38+
3639
var gamePathExplicit: Boolean = false
3740

3841
fun setProjectDir(dir: Path) {

src/test/kotlin/GenerateTests.kt

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import config.ScriptMode
22
import file.CLICommand
33
import file.CoreJassProvider
4+
import file.CuratedDependencies
45
import file.ExitHandler
56
import file.SetupApp
67
import file.SetupMain
@@ -261,7 +262,7 @@ class GenerateTests {
261262
fun testGenerateWithoutNameUsesWizardPrompt() {
262263
val setup = SetupMain()
263264
setup.parseArgs(listOf("generate"))
264-
val answers = java.util.ArrayDeque(listOf("wizardproject", "jass", "pre1.29", "none", "y", "y"))
265+
val answers = java.util.ArrayDeque(listOf("wizardproject", "jass", "pre1.29", "none", "y", "y", "n"))
265266
val prevPrompt = SetupApp.generatePrompt
266267
try {
267268
SetupApp.generatePrompt = { _, _ -> answers.removeFirst() }
@@ -275,13 +276,14 @@ class GenerateTests {
275276
Assert.assertEquals(setup.wc3Patch, CoreJassProvider.PRE_129_PATCH)
276277
Assert.assertTrue(setup.addAgents)
277278
Assert.assertTrue(setup.addGithubWorkflow)
279+
Assert.assertTrue(setup.curatedDependencyIds.isEmpty())
278280
}
279281

280282
@Test(priority = 10)
281283
fun testGenerateWizardRejectsUnsupportedScriptModeAndPatchInput() {
282284
val setup = SetupMain()
283285
setup.parseArgs(listOf("generate"))
284-
val answers = java.util.ArrayDeque(listOf("wizardproject", "t", "jass", "t", "2", "none", "n", "n"))
286+
val answers = java.util.ArrayDeque(listOf("wizardproject", "t", "jass", "t", "2", "none", "n", "n", "n"))
285287
val prevPrompt = SetupApp.generatePrompt
286288
try {
287289
SetupApp.generatePrompt = { _, _ -> answers.removeFirst() }
@@ -301,7 +303,7 @@ class GenerateTests {
301303
fun testGenerateWizardPreservesCliPatchDefault() {
302304
val setup = SetupMain()
303305
setup.parseArgs(listOf("generate", "--wc3-patch", "pre1.29"))
304-
val answers = java.util.ArrayDeque(listOf("wizardproject", "", "", "none", "n", "n"))
306+
val answers = java.util.ArrayDeque(listOf("wizardproject", "", "", "none", "n", "n", "n"))
305307
val prevPrompt = SetupApp.generatePrompt
306308
try {
307309
SetupApp.generatePrompt = { _, _ -> answers.removeFirst() }
@@ -333,6 +335,57 @@ class GenerateTests {
333335
Assert.assertTrue(setup.commandArg.isBlank())
334336
}
335337

338+
@Test(priority = 10)
339+
fun testCuratedDependencyCatalogResolvesTableLayout() {
340+
val dep = CuratedDependencies.findById("table-layout")
341+
Assert.assertNotNull(dep)
342+
Assert.assertEquals(dep!!.url, "https://github.com/Frotty/wurst-table-layout")
343+
Assert.assertEquals(dep.summary, "wurst-table-layout (AI ready UI toolkit)")
344+
Assert.assertEquals(CuratedDependencies.findById("TABLE-LAYOUT")?.id, "table-layout")
345+
Assert.assertEquals(
346+
CuratedDependencies.matching(listOf(dep.url)).map { it.id },
347+
listOf("table-layout")
348+
)
349+
}
350+
351+
@Test(priority = 10)
352+
fun testGenerateWithDepFlagSelectsCuratedDependency() {
353+
val setup = SetupMain()
354+
setup.parseArgs(listOf("generate", "myproject", "--with-dep", "table-layout"))
355+
Assert.assertEquals(setup.curatedDependencyIds, listOf("table-layout"))
356+
}
357+
358+
@Test(priority = 10)
359+
fun testGenerateWithDepFlagIsDeduplicated() {
360+
val setup = SetupMain()
361+
setup.parseArgs(listOf("generate", "myproject", "--with-dep", "table-layout", "--with-dep", "table-layout"))
362+
Assert.assertEquals(setup.curatedDependencyIds, listOf("table-layout"))
363+
}
364+
365+
@Test(priority = 10)
366+
fun testGenerateWithUnknownDepFlagExitsCleanly() {
367+
val code = catchExit2 {
368+
SetupMain().parseArgs(listOf("generate", "myproject", "--with-dep", "does-not-exist"))
369+
}
370+
Assert.assertEquals(code, 1, "An unknown curated dependency id should be reported as a CLI error")
371+
}
372+
373+
@Test(priority = 10)
374+
fun testGenerateWizardSelectsCuratedDependency() {
375+
val setup = SetupMain()
376+
setup.parseArgs(listOf("generate"))
377+
val answers = java.util.ArrayDeque(listOf("wizardproject", "lua", "", "none", "n", "n", "y"))
378+
val prevPrompt = SetupApp.generatePrompt
379+
try {
380+
SetupApp.generatePrompt = { _, _ -> answers.removeFirst() }
381+
Assert.assertTrue(SetupApp.prepareGenerate(setup))
382+
} finally {
383+
SetupApp.generatePrompt = prevPrompt
384+
}
385+
386+
Assert.assertEquals(setup.curatedDependencyIds, listOf("table-layout"))
387+
}
388+
336389
@Test(priority = 10)
337390
fun testNoArgsExitsZeroWithoutOldUi() {
338391
val code = catchExit2 { SetupMain().doMain(arrayOf()) }

0 commit comments

Comments
 (0)