Skip to content

Commit

Permalink
feat: Catalog templates (#748)
Browse files Browse the repository at this point in the history
  • Loading branch information
quintesse authored Mar 17, 2021
1 parent d7c979a commit 80674dd
Show file tree
Hide file tree
Showing 44 changed files with 2,615 additions and 1,582 deletions.
2 changes: 1 addition & 1 deletion itests/run.feature
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Feature: run

Scenario: should fail on missing file
* command('jbang notthere.java')
* match err contains 'Could not read script argument notthere.java'
* match err contains 'Script or alias could not be found or read: \'notthere.java\''
* match exit == 2

Scenario: parameter passing
Expand Down
55 changes: 55 additions & 0 deletions readme.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,61 @@ To get started you can run `jbang init helloworld.java` and a simple java class

Using `jbang init --template=cli helloworld.java` you get a more complete Hello World CLI using https://picocli.info/[picocli] as dependency.

Run `jbang template list` to see the full list of templates that are available.

It's also possible to create your own templates using the `jbang template add` command. For example, running:

[source,bash]
----
$ jbang template add --name logo showlogo.java img.jpg some.properties
----

Would add a template named "logo" with 3 files which could then be instantiated running `jbang init -t=logo mylogo`.

When instantiating a template the paths of the source files are ignored. So the following template:

[source,bash]
----
$ jbang template add --name logo src/showlogo.java images/img.jpg resources/some.properties
----

Has the exact same result as the previous example.

It's also possible to give the instantiated files (the targets) different names or different paths than their originals
(the sources), like this:

[source,bash]
----
$ jbang template add --name logo \
src/showlogo.java=showlogo.java \
img/img.jpg=img.jpg \
resources/logo.properties=some.properties
----

Btw, if you'd try to run the last command (and assuming the source files would exist) you'd get an error saying:

[source,bash]
----
$ jbang template add --name logo ...
[jbang] [ERROR] A target pattern is required. Prefix at least one of the files with '{filename}=' or '{basename}.ext='
----

This is because at least one of the files needs a target (the part before the `=` sign) that contains a "pattern".
That pattern is the part of the name that will be replaced with the name that you pass to `jbang init`
(if you type `jbang init helloworld.java` any occurrence of `{filename}` would be replaced with `helloworld.java`,
while any occurrence of `{basename}` would be replaced with `helloworld`).

If you don't specify a "target patterns" for any of the file Jbang will try to pick one for you.
Basically if the first file you specify doesn't have a target it will use that and add a pattern.
You will see something like this if it was successful:

[source,bash]
----
$ jbang template add showlogo.java img.jpg some.properties
[jbang] No explicit target pattern was set, using first file: {basename}.java=showlogo.java
[jbang] Template 'showlogo' added to '.../jbang-catalog.json'
----

== Declare dependencies

If you want to write real scripts you will want to use some java libraries.
Expand Down
6 changes: 3 additions & 3 deletions src/main/java/dev/jbang/Settings.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import java.nio.file.Path;
import java.nio.file.Paths;

import dev.jbang.catalog.AliasUtil;
import dev.jbang.catalog.Catalog;

public class Settings {
public static final String JBANG_REPO = "JBANG_REPO";
Expand Down Expand Up @@ -105,11 +105,11 @@ public static Path getTrustedSourcesFile() {
}

public static Path getUserCatalogFile() {
return getConfigDir().resolve(AliasUtil.JBANG_CATALOG_JSON);
return getConfigDir().resolve(Catalog.JBANG_CATALOG_JSON);
}

public static Path getUserImplicitCatalogFile() {
return getConfigDir().resolve(AliasUtil.JBANG_IMPLICIT_CATALOG_JSON);
return getConfigDir().resolve(Catalog.JBANG_IMPLICIT_CATALOG_JSON);
}

}
113 changes: 94 additions & 19 deletions src/main/java/dev/jbang/catalog/Alias.java
Original file line number Diff line number Diff line change
@@ -1,53 +1,128 @@
package dev.jbang.catalog;

import static dev.jbang.cli.BaseCommand.EXIT_INVALID_INPUT;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

import com.google.gson.annotations.SerializedName;

import dev.jbang.cli.ExitException;
import dev.jbang.util.Util;

public class Alias {
public class Alias extends CatalogItem {
@SerializedName(value = "script-ref", alternate = { "scriptRef" })
public final String scriptRef;
public final String description;
public final List<String> arguments;
public final Map<String, String> properties;
public transient Catalog catalog;

public Alias(String scriptRef, String description, List<String> arguments, Map<String, String> properties,
Catalog catalog) {
super(catalog);
this.scriptRef = scriptRef;
this.description = description;
this.arguments = arguments;
this.properties = properties;
this.catalog = catalog;
}

/**
* This method returns the scriptRef of the Alias with all contextual modifiers
* like baseRefs and current working directories applied.
*/
public String resolve(Path cwd) {
if (cwd == null) {
cwd = Util.getCwd();
public String resolve() {
return resolve(scriptRef);
}

/**
* Returns an Alias object for the given name
*
* @param aliasName The name of an Alias
* @return An Alias object or null if no alias was found
*/
public static Alias get(String aliasName) {
return get(aliasName, null, null);
}

/**
* Returns an Alias object for the given name with the given arguments and
* properties applied to it. Or null if no alias with that name could be found.
*
* @param aliasName The name of an Alias
* @param arguments Optional arguments to apply to the Alias
* @param properties Optional properties to apply to the Alias
* @return An Alias object or null if no alias was found
*/
public static Alias get(String aliasName, List<String> arguments, Map<String, String> properties) {
HashSet<String> names = new HashSet<>();
Alias alias = new Alias(null, null, arguments, properties, null);
Alias result = merge(alias, aliasName, names);
return result.scriptRef != null ? result : null;
}

private static Alias merge(Alias a1, String name, HashSet<String> names) {
if (names.contains(name)) {
throw new RuntimeException("Encountered alias loop on '" + name + "'");
}
String baseRef = catalog.getScriptBase();
String ref = scriptRef;
if (!AliasUtil.isAbsoluteRef(ref)) {
ref = baseRef + "/" + ref;
String[] parts = name.split("@");
if (parts.length > 2 || parts[0].isEmpty()) {
throw new RuntimeException("Invalid alias name '" + name + "'");
}
if (!AliasUtil.isRemoteRef(ref)) {
Path script = Paths.get(ref).normalize();
if (cwd.getRoot().equals(script.getRoot())) {
script = cwd.relativize(script);
} else {
script = script.toAbsolutePath();
Alias a2;
if (parts.length == 1) {
a2 = getLocal(name);
} else {
if (parts[1].isEmpty()) {
throw new RuntimeException("Invalid alias name '" + name + "'");
}
ref = script.toString();
a2 = getCatalogAlias(parts[1], parts[0]);
}
if (a2 != null) {
names.add(name);
a2 = merge(a2, a2.scriptRef, names);
List<String> args = a1.arguments != null && !a1.arguments.isEmpty() ? a1.arguments : a2.arguments;
Map<String, String> props = a1.properties != null && !a1.properties.isEmpty() ? a1.properties
: a2.properties;
Catalog catalog = a2.catalog != null ? a2.catalog : a1.catalog;
return new Alias(a2.scriptRef, a2.description, args, props, catalog);
} else {
return a1;
}
return ref;
}

/**
* Returns the given Alias from the local file system
*
* @param aliasName The name of an Alias
* @return An Alias object
*/
private static Alias getLocal(String aliasName) {
Catalog catalog = findNearestCatalogWithAlias(Util.getCwd(), aliasName);
if (catalog != null) {
return catalog.aliases.getOrDefault(aliasName, null);
}
return null;
}

/**
* Returns the given Alias from the given registered Catalog
*
* @param catalogName The name of a registered Catalog
* @param aliasName The name of an Alias
* @return An Alias object
*/
private static Alias getCatalogAlias(String catalogName, String aliasName) {
Catalog catalog = Catalog.getByName(catalogName);
Alias alias = catalog.aliases.get(aliasName);
if (alias == null) {
throw new ExitException(EXIT_INVALID_INPUT, "No alias found with name '" + aliasName + "'");
}
return alias;
}

static Catalog findNearestCatalogWithAlias(Path dir, String aliasName) {
return Catalog.findNearestCatalogWith(dir, catalog -> catalog.aliases.containsKey(aliasName));
}
}
Loading

0 comments on commit 80674dd

Please sign in to comment.