Skip to content

Commit 89b591f

Browse files
committed
feat: working recurse command in the new style, misc cleanup and utility
1 parent 85433d5 commit 89b591f

File tree

9 files changed

+162
-98
lines changed

9 files changed

+162
-98
lines changed

gradle/gooeycli/src/main/groovy/org/terasology/cli/commands/GetCommand.groovy

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import org.terasology.cli.options.GitOptions
66
import org.terasology.cli.managers.ManagedItem
77
import picocli.CommandLine.ParentCommand
88
import picocli.CommandLine.Command
9-
// Actually in use, annotation below may show syntax error due to Groovy's annotation by the same name. Works fine
109
import picocli.CommandLine.Mixin
1110
import picocli.CommandLine.Parameters
1211

@@ -34,8 +33,9 @@ class GetCommand extends BaseCommandType implements Runnable {
3433

3534
// The parent should be a ManagedItem. Make an instace including the possible git origin option
3635
ManagedItem mi = parent.getManager(gitOptions.origin)
36+
println "Got a parent command, associated item is: " + mi.getDisplayName()
3737

3838
// Having prepared an instance of the logic class we call it to actually retrieve stuff
39-
mi.retrieve(items, false)
39+
mi.get(items)
4040
}
4141
}

gradle/gooeycli/src/main/groovy/org/terasology/cli/commands/InitCommand.groovy

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import picocli.CommandLine.Help.Ansi
1010
import picocli.CommandLine.Mixin
1111
import picocli.CommandLine.Parameters
1212

13+
import static org.terasology.cli.helpers.KitchenSink.green
14+
1315
@Command(name = "init", description = "Initializes a workspace with some useful things")
1416
class InitCommand extends BaseCommandType implements Runnable {
1517

@@ -26,6 +28,7 @@ class InitCommand extends BaseCommandType implements Runnable {
2628
System.out.println(str)
2729
println "Do we have a Git origin override? " + gitOptions.origin
2830
println "Can has desired global prop? " + PropHelper.getGlobalProp("alternativeGithubHome")
31+
green "Some green text"
2932
// Call logic elsewhere
3033
}
3134
}

gradle/gooeycli/src/main/groovy/org/terasology/cli/commands/ModuleCommand.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import org.terasology.cli.managers.ManagedModule
77
import picocli.CommandLine.Command
88
import picocli.CommandLine.HelpCommand
99

10-
// If using local groovy files without Gradle the subcommands section may highlight as bad syntax in IntelliJ - that's OK
1110
@Command(name = "module",
1211
synopsisSubcommandLabel = "COMMAND", // Default is [COMMAND] indicating optional, but sub command here is required
1312
subcommands = [
@@ -17,6 +16,7 @@ import picocli.CommandLine.HelpCommand
1716
GetCommand.class], // Note that these Groovy classes *must* start with a capital letter for some reason
1817
description = "Sub command for interacting with modules")
1918
class ModuleCommand extends ItemCommandType {
19+
2020
@Override
2121
ManagedItem getManager(String optionGitOrigin) {
2222
return new ManagedModule(optionGitOrigin)

gradle/gooeycli/src/main/groovy/org/terasology/cli/commands/RecurseCommand.groovy

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// SPDX-License-Identifier: Apache-2.0
33
package org.terasology.cli.commands
44

5+
import org.terasology.cli.managers.ManagedItem
56
import org.terasology.cli.options.GitOptions
67
import picocli.CommandLine.ParentCommand
78
import picocli.CommandLine.Command
@@ -27,6 +28,12 @@ class RecurseCommand extends BaseCommandType implements Runnable {
2728

2829
void run() {
2930
println "Going to recurse $items! And from origin: " + gitOptions.origin
30-
println "Command parent is: " + parent.getItemType()
31+
32+
// The parent should be a ManagedItem. Make an instance including the possible git origin option
33+
ManagedItem mi = parent.getManager(gitOptions.origin)
34+
println "Got a parent command, associated item is: " + mi.getDisplayName()
35+
36+
// Having prepared an instance of the logic class we call it to actually retrieve stuff
37+
mi.recurse(items)
3138
}
3239
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright 2020 The Terasology Foundation
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package org.terasology.cli.helpers
5+
6+
import picocli.CommandLine
7+
8+
class KitchenSink {
9+
/**
10+
* Tests a URL via a HEAD request (no body) to see if it is valid
11+
* @param url the URL to test
12+
* @return boolean indicating whether the URL is valid (code 200) or not
13+
*/
14+
static boolean isUrlValid(String url) {
15+
def code = new URL(url).openConnection().with {
16+
requestMethod = 'HEAD'
17+
connect()
18+
responseCode
19+
}
20+
return code.toString() == "200"
21+
}
22+
23+
// Logging methods to produce colored text. Other styling is inconsistent, for instance underline on Windows sets gray background instead
24+
25+
/**
26+
* Simply logs text in green
27+
* @param message the message to color
28+
*/
29+
static void green(String message) {
30+
System.out.println(CommandLine.Help.Ansi.AUTO.string("@|green $message|@"))
31+
}
32+
33+
/**
34+
* Simply logs text in yellow
35+
* @param message the message to color
36+
*/
37+
static void yellow(String message) {
38+
System.out.println(CommandLine.Help.Ansi.AUTO.string("@|yellow $message|@"))
39+
}
40+
41+
/**
42+
* Simply logs text in red
43+
* @param message the message to color
44+
*/
45+
static void red(String message) {
46+
System.out.println(CommandLine.Help.Ansi.AUTO.string("@|red $message|@"))
47+
}
48+
49+
50+
}

gradle/gooeycli/src/main/groovy/org/terasology/cli/managers/DependencyProvider.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@ package org.terasology.cli.managers
77
* Marks a managed item as having the capacity to have dependencies, forcing implementation of a way to parse them.
88
*/
99
interface DependencyProvider {
10-
def parseDependencies(String itemsToCheck)
10+
List<String> parseDependencies(File targetDirectory, String itemToCheck)
1111
}
Lines changed: 47 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
// Copyright 2020 The Terasology Foundation
22
// SPDX-License-Identifier: Apache-2.0
33
package org.terasology.cli.managers
4-
@GrabResolver(name = 'jcenter', root = 'http://jcenter.bintray.com/')
5-
@Grab(group = 'org.ajoberstar', module = 'grgit', version = '1.9.3')
6-
import org.ajoberstar.grgit.Grgit
4+
75
import org.terasology.cli.helpers.PropHelper
6+
import org.terasology.cli.scm.ScmGet
7+
8+
import static org.terasology.cli.helpers.KitchenSink.green
9+
import static org.terasology.cli.helpers.KitchenSink.yellow
810

911
/**
1012
* Utility class for dealing with items managed in a developer workspace.
@@ -25,32 +27,37 @@ abstract class ManagedItem {
2527
File targetDirectory
2628
abstract File getTargetDirectory()
2729

28-
String githubTargetHome
30+
String targetGitOrigin
2931
abstract String getDefaultItemGitOrigin()
3032

3133
ManagedItem() {
3234
displayName = getDisplayName()
3335
targetDirectory = getTargetDirectory()
34-
githubTargetHome = calculateGitOrigin(null)
36+
targetGitOrigin = calculateGitOrigin(null)
3537
}
3638

3739
ManagedItem(String optionGitOrigin) {
3840
displayName = getDisplayName()
3941
targetDirectory = getTargetDirectory()
40-
githubTargetHome = calculateGitOrigin(optionGitOrigin)
42+
targetGitOrigin = calculateGitOrigin(optionGitOrigin)
4143
}
4244

45+
/**
46+
* Initializer for the target origin to get resources from - option, workspace setting, or a default.
47+
* @param optionOrigin an alternative if the user submitted a git option via CLI
48+
* @return the highest priority override or the default
49+
*/
4350
String calculateGitOrigin(String optionOrigin) {
4451
// If the user indicated a target Git origin via option parameter then use that (primary choice)
4552
if (optionOrigin != null) {
46-
println "We have an option set for Git origin so using that: " + optionOrigin
53+
green "We have an option set for Git origin so using that: " + optionOrigin
4754
return optionOrigin
4855
}
4956

5057
// Alternatively if the user has a global override set for Git origin then use that (secondary choice)
5158
String altOrigin = PropHelper.getGlobalProp("alternativeGithubHome")
5259
if (altOrigin != null) {
53-
println "There was no option set but we have a global proper override for Git origin: " + altOrigin
60+
green "There was no option set but we have a global proper override for Git origin: " + altOrigin
5461
return altOrigin
5562
}
5663

@@ -59,110 +66,65 @@ abstract class ManagedItem {
5966
return getDefaultItemGitOrigin()
6067
}
6168

62-
// TODO: Likely everything below should just delegate to more specific classes to keep things tidy
63-
// TODO: That would allow these methods to later just figure out exact required operations then delegate
64-
// TODO: Should make it easier to hide the logic of (for instance) different Git adapters behind the next step
65-
6669
/**
67-
* Tests a URL via a HEAD request (no body) to see if it is valid
68-
* @param url the URL to test
69-
* @return boolean indicating whether the URL is valid (code 200) or not
70+
* The Get command simply retrieves one or more resources in one go. Easy!
71+
* @param items to retrieve
7072
*/
71-
boolean isUrlValid(String url) {
72-
def code = new URL(url).openConnection().with {
73-
requestMethod = 'HEAD'
74-
connect()
75-
responseCode
73+
def get(List<String> items) {
74+
for (String itemName : items) {
75+
println "Going to retrieve $displayName $itemName"
76+
getItem(itemName)
7677
}
77-
return code.toString() == "200"
7878
}
7979

80+
/**
81+
* More advanced version of the Get command that also retrieves any dependencies defined by the items.
82+
* @param items to retrieve
83+
* @return discovered dependencies from one round of processing (if used recursively)
84+
*/
8085
List<String> recurse(List<String> items) {
81-
def dependencies = []
82-
println "Going to retrieve the following items (recursively): $items"
86+
List<String> dependencies = []
87+
println "Going to retrieve the following $displayName item(s) recursively: $items"
8388
for (String item : items) {
8489
// Check for circular dependencies - we should only ever act on a request to *retrieve* an item once
8590
if (itemsRetrieved.contains(item)) {
86-
println "Uh oh, we got told to re-retrieve the same thing for $item - somebody oopsed a circular dependency? Skipping"
91+
yellow "Uh oh, we got told to re-retrieve the same thing for $item - somebody wrote a circular dependency? Skipping"
8792
} else {
8893
// We didn't already try to retrieve this item: get it (if we already have it then getItem will just be a no-op)
8994
getItem(item)
9095
// Then goes and checks the item on disk and parses the thing to see if it has dependencies (even if we already had it)
91-
dependencies << ((DependencyProvider) this).parseDependencies(item)
96+
def newDependencyCandidates = ((DependencyProvider) this).parseDependencies(targetDirectory, item)
97+
println "Got new dependency candidates: " + newDependencyCandidates
98+
dependencies += newDependencyCandidates - itemsRetrieved - dependencies
99+
println "Storing them without those already retrieved: " + dependencies
92100
}
93-
// Mark this item as retrieved just in case somebody made a whoops and introduced a circular dependency
101+
// Mark this item as retrieved - that way we'll disqualify it if it comes up again in the future
94102
itemsRetrieved << item
95103
}
96104

105+
println "Finished recursively retrieving the following list: " + items
106+
dependencies -= itemsRetrieved
107+
println "After disqualifying any dependencies that were already in that list the following remains: " + dependencies
108+
97109
// If we parsed any dependencies, retrieve them recursively (and even if we already got them check them for dependencies as well)
98110
if (!dependencies.isEmpty()) {
99-
println "Got dependencies to fetch: " + dependencies
111+
println "Got dependencies to fetch so we'll recurse and go again!"
100112
return recurse(dependencies)
101113
}
102114

103-
println "Finally done recursing, both initial items and any parsed dependencies"
115+
green "Finally done recursing, both initial items and any parsed dependencies"
104116
return null
105117
}
106118

107-
def get(List<String> items) {
108-
for (String itemName : items) {
109-
getItem(itenName)
110-
}
111-
}
112-
113-
def getItem(String item) {
114-
println "Going to get $item do we already have it? <logic to look for the dir existing>"
115-
// Logic for a single retrieve, no dependency parsing involved
116-
}
117-
118119
/**
119-
* Primary entry point for retrieving items, kicks off recursively if needed.
120-
* @param items the items we want to retrieve
121-
* @param recurse whether to also retrieve dependencies of the desired items (only really for modules ...)
120+
* Simple one-item execution point for attempting to get a resource.
121+
* @param item the resource to get
122122
*/
123-
def retrieve(List<String> items, boolean recurse) {
124-
println "Now inside retrieve, user (recursively? $recurse) wants: $items"
125-
for (String itemName : items) {
126-
println "Starting retrieval for $displayName $itemName, are we recursing? $recurse"
127-
println "Retrieved so far: $itemsRetrieved"
128-
retrieveItem(itemName, recurse)
129-
}
130-
}
123+
void getItem(String item) {
124+
println "Processing get request for $item via SCM"
125+
// Logic for a single retrieve, no dependency parsing involved, nor Git origin tweaking - already handled
126+
ScmGet.cloneRepo(item, targetGitOrigin, targetDirectory, displayName)
131127

132-
/**
133-
* Retrieves a single item via Git Clone. Considers whether it exists locally first or if it has already been retrieved this execution.
134-
* @param itemName the target item to retrieve
135-
* @param recurse whether to also retrieve its dependencies (if so then recurse back into retrieve)
136-
*/
137-
def retrieveItem(String itemName, boolean recurse) {
138-
File itemDir = new File(targetDirectory, itemName)
139-
println "Request to retrieve $displayName $itemName would store it at $itemDir - exists? " + itemDir.exists()
140-
if (itemDir.exists()) {
141-
println "That $displayName already had an existing directory locally. If something is wrong with it please delete and try again"
142-
itemsRetrieved << itemName
143-
} else if (itemsRetrieved.contains(itemName)) {
144-
println "We already retrieved $itemName - skipping"
145-
} else {
146-
itemsRetrieved << itemName
147-
def targetUrl = "https://github.com/${githubTargetHome}/${itemName}"
148-
if (!isUrlValid(targetUrl)) {
149-
println "Can't retrieve $displayName from $targetUrl - URL appears invalid. Typo? Not created yet?"
150-
return
151-
}
152-
println "Retrieving $displayName $itemName from $targetUrl"
153-
if (githubTargetHome != getDefaultItemGitOrigin()) {
154-
println "Doing a retrieve from a custom remote: $githubTargetHome - will name it as such plus add the ${getDefaultItemGitOrigin()} remote as '$defaultRemote'"
155-
Grgit.clone dir: itemDir, uri: targetUrl, remote: githubTargetHome
156-
println "Primary clone operation complete, about to add the '$defaultRemote' remote for the ${getDefaultItemGitOrigin()} org address"
157-
//addRemote(itemName, defaultRemote, "https://github.com/${getDefaultItemGitOrigin()}/${itemName}") //TODO: Add me :p
158-
} else {
159-
println "Cloning $targetUrl to $itemDir"
160-
Grgit.clone dir: itemDir, uri: targetUrl
161-
}
162-
/*
163-
// This step allows the item type to check the newly cloned item and add in extra template stuff - TODO? Same as the recurse fix?
164-
//itemTypeScript.copyInTemplateFiles(itemDir)
165-
*/
166-
}
128+
// TODO: Consider supporting copying in template files at this point if the type requests that
167129
}
168130
}

gradle/gooeycli/src/main/groovy/org/terasology/cli/managers/ManagedModule.groovy

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22
// SPDX-License-Identifier: Apache-2.0
33
package org.terasology.cli.managers
44

5+
import groovy.json.JsonSlurper
6+
57
class ManagedModule extends ManagedItem implements DependencyProvider {
8+
// TODO: Check these - why would they show up under modules/ ? Other than Index maybe?
9+
def excludedItems = ["engine", "Index", "out", "build"]
10+
611
ManagedModule() {
712
super()
813
}
@@ -26,13 +31,30 @@ class ManagedModule extends ManagedItem implements DependencyProvider {
2631
return "Terasology"
2732
}
2833

29-
@Override
30-
List<String> parseDependencies(String itemToCheck) {
31-
List<String> foundDependencies = []
32-
33-
// logic to parse module.txt for dependencies
34-
35-
return foundDependencies
34+
/**
35+
* Reads a given module info file to figure out which if any dependencies it has. Filters out any already retrieved.
36+
* This method is only for modules.
37+
* @param targetModuleInfo the target file to check (a module.txt file or similar)
38+
* @return a String[] containing the next level of dependencies, if any
39+
*/
40+
List<String> parseDependencies(File targetDirectory, String itemName, boolean respectExcludedItems = true) {
41+
def qualifiedDependencies = []
42+
File targetModuleInfo = new File(targetDirectory, itemName + "/module.txt")
43+
if (!targetModuleInfo.exists()) {
44+
println "The module info file did not appear to exist - can't calculate dependencies"
45+
return qualifiedDependencies
46+
}
47+
def slurper = new JsonSlurper()
48+
def moduleConfig = slurper.parseText(targetModuleInfo.text)
49+
for (dependency in moduleConfig.dependencies) {
50+
if (respectExcludedItems && excludedItems.contains(dependency.id)) {
51+
println "Skipping listed dependency $dependency.id as it is in the exclude list (shipped with primary project)"
52+
} else {
53+
println "Accepting listed dependency $dependency.id"
54+
qualifiedDependencies << dependency.id
55+
}
56+
}
57+
return qualifiedDependencies
3658
}
3759

3860
/**

0 commit comments

Comments
 (0)