dev.jbang.cli.Init Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jbang-cli Show documentation
Show all versions of jbang-cli Show documentation
JBang Command Line Interface
package dev.jbang.cli;
import static dev.jbang.util.Util.entry;
import static java.lang.System.getenv;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;
import javax.lang.model.SourceVersion;
import com.google.gson.Gson;
import dev.jbang.catalog.TemplateProperty;
import dev.jbang.source.RefTarget;
import dev.jbang.source.ResourceRef;
import dev.jbang.source.resolvers.SiblingResourceResolver;
import dev.jbang.util.TemplateEngine;
import dev.jbang.util.Util;
import io.quarkus.qute.Template;
import io.quarkus.qute.TemplateInstance;
import picocli.CommandLine;
@CommandLine.Command(name = "init", description = "Initialize a script.")
public class Init extends BaseCommand {
@CommandLine.Option(names = { "--template",
"-t" }, description = "Init script with a java class useful for scripting")
public String initTemplate;
@CommandLine.Option(names = {
"--force" }, description = "Force overwrite of existing files")
public boolean force;
@CommandLine.Option(names = { "--edit" }, description = "Open editor on the generated file(s)")
public boolean edit;
@CommandLine.Option(names = { "-D" }, description = "set a system property", mapFallbackValue = "true")
public Map properties = new HashMap<>();
@CommandLine.Option(names = {
"--deps" }, converter = CommaSeparatedConverter.class, description = "Add additional dependencies (Use commas to separate them).")
List dependencies;
@CommandLine.Parameters(paramLabel = "scriptOrFile", index = "0", description = "A file or URL to a Java code file", arity = "1")
String scriptOrFile;
@CommandLine.Parameters(paramLabel = "params", index = "1..*", arity = "0..*", description = "Parameters to pass on to the generation")
List params = new ArrayList<>();
public void requireScriptArgument() {
if (scriptOrFile == null) {
throw new IllegalArgumentException("Missing required parameter: ''");
}
}
@Override
public Integer doCall() throws IOException {
requireScriptArgument();
dev.jbang.catalog.Template tpl = dev.jbang.catalog.Template.get(initTemplate);
if (tpl == null) {
throw new ExitException(BaseCommand.EXIT_INVALID_INPUT,
"Could not find init template named: " + initTemplate
+ ". Try run with --fresh to get latest catalog updates.");
}
boolean absolute = new File(scriptOrFile).isAbsolute();
Path outFile = Util.getCwd().resolve(scriptOrFile);
Path outDir = outFile.getParent();
String outName = outFile.getFileName().toString();
String baseName = Util.getBaseName(Paths.get(scriptOrFile).getFileName().toString());
String extension = Util.extension(scriptOrFile);
properties.put("scriptref", scriptOrFile);
properties.put("baseName", baseName);
properties.put("dependencies", dependencies);
// properties.put("magiccontent", "//no gpt response. make sure you ran with
// --preview and OPENAI_API_KEY set");
if (Util.isPreview() && !params.isEmpty()) {
Util.infoMsg("JBangGPT Preview activated");
Util.warnMsg(
"The result can vary greatly. Sometimes it works - other times it is just for inspiration or a good laugh.");
String openaiKey = getenv("OPENAI_API_KEY");
if (openaiKey != null && !openaiKey.trim().isEmpty()) {
String response = fetchGptResponse(baseName, extension, String.join(" ", params), openaiKey);
// sometimes gpt adds a markdown ```java block so lets remove all lines starting
// with ``` in the output.
response = response.replaceAll("(?m)^```.*(?:\r?\n|$)", "");
properties.put("magiccontent", response);
} else {
Util.warnMsg("OPENAI_API_KEY environment variable not found. Will use normal jbang init.");
}
}
List refTargets = tpl.fileRefs .entrySet()
.stream()
.map(e -> entry(
resolveBaseName(e.getKey(), e.getValue(), outName),
tpl.resolve(e.getValue())))
.map(e -> RefTarget.create(
e.getValue(),
e.getKey(),
new SiblingResourceResolver(tpl.catalog.catalogRef)))
.collect(Collectors.toList());
applyTemplateProperties(tpl);
if (!force) {
// Check if any of the files already exist
for (RefTarget refTarget : refTargets) {
Path target = refTarget.to(outDir);
if (Files.exists(target)) {
warn("File " + target + " already exists. Will not initialize.");
return EXIT_GENERIC_ERROR;
}
}
}
try {
for (RefTarget refTarget : refTargets) {
if (refTarget.getSource().getOriginalResource().endsWith(".qute")) {
// TODO fix outFile path handling
Path out = refTarget.to(outDir);
renderQuteTemplate(out, refTarget.getSource(), properties);
} else {
refTarget.copy(outDir);
}
}
} catch (IOException e) {
// Clean up any files we already created
for (RefTarget refTarget : refTargets) {
Util.deletePath(refTarget.to(outDir), true);
}
}
String renderedScriptOrFile = getRenderedScriptOrFile(tpl.fileRefs, refTargets, outDir, absolute);
if (edit) {
info("File initialized. Opening editor for you. You can also now run it with 'jbang "
+ renderedScriptOrFile);
// TODO: quick hack that gets the job of opening editor done; but really should
// make a isolated api to open editor instead of invoking subcommand.
// nice thing wit this is that it will honor you jbang config for edit
// automatically.
JBang.getCommandLine().execute("edit", renderedScriptOrFile);
} else {
info("File initialized. You can now run it with 'jbang " + renderedScriptOrFile
+ "' or edit it using 'jbang edit --open=[editor] "
+ renderedScriptOrFile + "' where [editor] is your editor or IDE, e.g. '"
+ Edit.knownEditors[new Random().nextInt(Edit.knownEditors.length)]
+ "'. If your IDE supports JBang, you can edit the directory instead: 'jbang edit . "
+ renderedScriptOrFile + "'. See https://jbang.dev/ide");
}
return EXIT_OK;
}
static class GPTResponse {
public String id;
public String object;
public double created;
public String model;
public Map usage;
public List choices;
static public class Choice {
public Message message;
public static class Message {
public String role;
public String content;
}
}
public Error error;
static public class Error {
String message;
String type;
String param;
String code;
@Override
public String toString() {
return type + ": " + message + " (code:" + code + "/param:" + param + ")";
}
}
}
public static String fetchGptResponse(String baseName, String extension, String request, String key) {
String answer = null;
try {
URL url = new URL("https://api.openai.com/v1/chat/completions");
HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
httpConn.setRequestMethod("POST");
httpConn.setRequestProperty("Content-Type", "application/json");
httpConn.setRequestProperty("Authorization", "Bearer " + key);
httpConn.setDoOutput(true);
OutputStreamWriter writer = new OutputStreamWriter(httpConn.getOutputStream());
Map prompt = new HashMap<>();
prompt.put("model", "gpt-3.5-turbo");
prompt.put("temperature", 0.8); // reduce variation, more deterministic
Gson gson = new Gson();
List