From ff4cf25585b67c1cd5607f5f5199c5a78d431167 Mon Sep 17 00:00:00 2001 From: Paul Trowbridge Date: Tue, 3 Mar 2026 15:59:04 -0500 Subject: [PATCH] Add ~/.jrunnerpass named connection profile support Implements .pgpass-style credential file for jrunner. Named aliases can be used with -sc and -dc flags instead of spelling out -scu/-scn/-scp for each invocation. Explicit flags still take priority over the file. Co-Authored-By: Claude Sonnet 4.6 --- jrunner/src/main/java/jrunner/jrunner.java | 94 +++++++++++++++++++++- 1 file changed, 90 insertions(+), 4 deletions(-) diff --git a/jrunner/src/main/java/jrunner/jrunner.java b/jrunner/src/main/java/jrunner/jrunner.java index 07c1ee1..80bc76f 100644 --- a/jrunner/src/main/java/jrunner/jrunner.java +++ b/jrunner/src/main/java/jrunner/jrunner.java @@ -5,6 +5,7 @@ import java.nio.file.Files; import java.nio.file.Path ; import java.nio.file.Paths; import java.time.*; +import java.io.IOException; public class jrunner { //static final String QUERY = "SELECT * from rlarp.osm LIMIT 100"; @@ -14,9 +15,11 @@ public class jrunner { String scu = ""; String scn = ""; String scp = ""; + String scAlias = ""; String dcu = ""; String dcn = ""; String dcp = ""; + String dcAlias = ""; String sq = ""; String dt = ""; Boolean trim = true; @@ -41,24 +44,36 @@ public class jrunner { Timestamp tsStart = null; Timestamp tsEnd = null; - msg = "jrunner version 1.1"; + msg = "jrunner version 1.2"; + msg = msg + nl + "-sc source connection alias (from ~/.jrunnerpass)"; msg = msg + nl + "-scu source jdbc url"; msg = msg + nl + "-scn source username"; - msg = msg + nl + "-scp source passowrd"; + msg = msg + nl + "-scp source password"; + msg = msg + nl + "-dc destination connection alias (from ~/.jrunnerpass)"; msg = msg + nl + "-dcu destination jdbc url"; msg = msg + nl + "-dcn destination username"; - msg = msg + nl + "-dcp destination passowrd"; + msg = msg + nl + "-dcp destination password"; msg = msg + nl + "-sq path to source query"; msg = msg + nl + "-dt fully qualified name of destination table"; msg = msg + nl + "-t trim text"; msg = msg + nl + "-c clear target table"; msg = msg + nl + "-f output format (csv, tsv, table, json) - default: csv"; msg = msg + nl + "--help info"; + msg = msg + nl + ""; + msg = msg + nl + "~/.jrunnerpass format:"; + msg = msg + nl + " [alias]"; + msg = msg + nl + " url=jdbc:..."; + msg = msg + nl + " user=username"; + msg = msg + nl + " pass=password"; //---------------------------------------parse args into variables------------------------------------------------- for (int i = 0; i < args.length; i = i +1 ){ switch (args[i]) { + //source connection alias + case "-sc": + scAlias = args[i+1]; + break; //source connection string case "-scu": scu = args[i+1]; @@ -68,10 +83,13 @@ public class jrunner { scn = args[i+1]; break; //source password - //import java.time.*; case "-scp": scp = args[i+1]; break; + //destination connection alias + case "-dc": + dcAlias = args[i+1]; + break; //destination connection string case "-dcu": dcu = args[i+1]; @@ -133,6 +151,31 @@ public class jrunner { } } + // Resolve connection aliases from ~/.jrunnerpass + if (!scAlias.isEmpty() || !dcAlias.isEmpty()) { + Map connections = loadPassFile(); + if (!scAlias.isEmpty()) { + String[] sc = connections.get(scAlias); + if (sc == null) { + System.err.println("Error: source alias '" + scAlias + "' not found in ~/.jrunnerpass"); + System.exit(1); + } + if (scu.isEmpty()) scu = sc[0]; + if (scn.isEmpty()) scn = sc[1]; + if (scp.isEmpty()) scp = sc[2]; + } + if (!dcAlias.isEmpty()) { + String[] dc = connections.get(dcAlias); + if (dc == null) { + System.err.println("Error: destination alias '" + dcAlias + "' not found in ~/.jrunnerpass"); + System.exit(1); + } + if (dcu.isEmpty()) dcu = dc[0]; + if (dcn.isEmpty()) dcn = dc[1]; + if (dcp.isEmpty()) dcp = dc[2]; + } + } + // Detect query mode when destination flags are not provided queryMode = dcu.isEmpty() && dcn.isEmpty() && dcp.isEmpty() && dt.isEmpty(); @@ -482,4 +525,47 @@ public class jrunner { private static String escapeTSV(String value) { return value.replaceAll("\t", " ").replaceAll("\n", " ").replaceAll("\r", " "); } + + // Loads ~/.jrunnerpass and returns a map of alias -> {url, user, pass} + // Format: + // [alias] + // url=jdbc:... + // user=username + // pass=password + private static Map loadPassFile() { + Map connections = new LinkedHashMap<>(); + Path passFile = Paths.get(System.getProperty("user.home"), ".jrunnerpass"); + if (!Files.exists(passFile)) { + System.err.println("Error: ~/.jrunnerpass not found"); + System.exit(1); + } + try { + String currentAlias = null; + String url = "", user = "", pass = ""; + for (String line : Files.readAllLines(passFile)) { + line = line.trim(); + if (line.isEmpty() || line.startsWith("#")) continue; + if (line.startsWith("[") && line.endsWith("]")) { + if (currentAlias != null) { + connections.put(currentAlias, new String[]{url, user, pass}); + } + currentAlias = line.substring(1, line.length() - 1).trim(); + url = ""; user = ""; pass = ""; + } else if (line.startsWith("url=")) { + url = line.substring(4); + } else if (line.startsWith("user=")) { + user = line.substring(5); + } else if (line.startsWith("pass=")) { + pass = line.substring(5); + } + } + if (currentAlias != null) { + connections.put(currentAlias, new String[]{url, user, pass}); + } + } catch (IOException e) { + System.err.println("Error reading ~/.jrunnerpass: " + e.getMessage()); + System.exit(1); + } + return connections; + } }