extensions/net.sf.basedb.opengrid/trunk/src/net/sf/basedb/opengrid/ScriptBuilder.java

Code
Comments
Other
Rev Date Author Line
4130 26 Sep 16 nicklas 1 package net.sf.basedb.opengrid;
4130 26 Sep 16 nicklas 2
5996 26 Aug 20 nicklas 3 import java.util.Arrays;
5996 26 Aug 20 nicklas 4 import java.util.Collection;
4130 26 Sep 16 nicklas 5 import java.util.HashMap;
5996 26 Aug 20 nicklas 6 import java.util.HashSet;
4130 26 Sep 16 nicklas 7 import java.util.Map;
5996 26 Aug 20 nicklas 8 import java.util.Set;
4130 26 Sep 16 nicklas 9
6627 07 Mar 22 nicklas 10 import net.sf.basedb.opengrid.filetransfer.StringUploadSource;
6627 07 Mar 22 nicklas 11 import net.sf.basedb.opengrid.filetransfer.UploadSource;
4130 26 Sep 16 nicklas 12
6627 07 Mar 22 nicklas 13
4130 26 Sep 16 nicklas 14 /**
4130 26 Sep 16 nicklas 15   Utility class for generating shell scripts.
4130 26 Sep 16 nicklas 16   @author nicklas
4130 26 Sep 16 nicklas 17   @since 1.0
4130 26 Sep 16 nicklas 18 */
4130 26 Sep 16 nicklas 19 public class ScriptBuilder 
4130 26 Sep 16 nicklas 20 {
4130 26 Sep 16 nicklas 21   private final StringBuilder script;
4130 26 Sep 16 nicklas 22   private final String logFolder;
4130 26 Sep 16 nicklas 23   
4130 26 Sep 16 nicklas 24   private int nextBgPid;
4130 26 Sep 16 nicklas 25   private Map<String, String> bgProcesses;
4130 26 Sep 16 nicklas 26   
5996 26 Aug 20 nicklas 27   private int indentLevel;
5996 26 Aug 20 nicklas 28   private String indentString;
5996 26 Aug 20 nicklas 29   private Set<String> autoIndent;
5996 26 Aug 20 nicklas 30   private Set<String> autoUnindent;
4130 26 Sep 16 nicklas 31   
4130 26 Sep 16 nicklas 32   /**
4130 26 Sep 16 nicklas 33     Create a new script builder using the working folder
4130 26 Sep 16 nicklas 34     for log and progress information.
4130 26 Sep 16 nicklas 35   */
4130 26 Sep 16 nicklas 36   public ScriptBuilder()
4130 26 Sep 16 nicklas 37   {
4130 26 Sep 16 nicklas 38     this(null);
4130 26 Sep 16 nicklas 39   }
4130 26 Sep 16 nicklas 40   
4130 26 Sep 16 nicklas 41   /**
4130 26 Sep 16 nicklas 42     Create a new script builder for a generic script.
4130 26 Sep 16 nicklas 43     @param logFolder Path to a folder to use for outputting 
4229 10 Nov 16 nicklas 44       log and progress information. If null, the folder set 
4270 16 Dec 16 nicklas 45       by ${WD} is used (=working directory for jobs executed by 
4229 10 Nov 16 nicklas 46       the Open Grid cluster).
4130 26 Sep 16 nicklas 47   */
4130 26 Sep 16 nicklas 48   public ScriptBuilder(String logFolder)
4130 26 Sep 16 nicklas 49   {
4130 26 Sep 16 nicklas 50     this.script = new StringBuilder();
4270 16 Dec 16 nicklas 51     this.logFolder = logFolder == null ? "${WD}" : logFolder;
5996 26 Aug 20 nicklas 52     this.indentLevel = 0;
5996 26 Aug 20 nicklas 53     this.indentString = "";
5996 26 Aug 20 nicklas 54     this.autoIndent = new HashSet<String>(Arrays.asList("if", "elif", "else", "case", "while", "until", "for"));
5996 26 Aug 20 nicklas 55     this.autoUnindent = new HashSet<String>(Arrays.asList("elif", "else", "fi", "esac", "done"));
4130 26 Sep 16 nicklas 56   }
4130 26 Sep 16 nicklas 57
4130 26 Sep 16 nicklas 58   /**
6648 21 Mar 22 nicklas 59     Join the other script with this script.
6648 21 Mar 22 nicklas 60     @since 1.5
6648 21 Mar 22 nicklas 61   */
6648 21 Mar 22 nicklas 62   public void join(ScriptBuilder other)
6648 21 Mar 22 nicklas 63   {
6648 21 Mar 22 nicklas 64     script.append(other.toString());
6648 21 Mar 22 nicklas 65   }
6648 21 Mar 22 nicklas 66   
6648 21 Mar 22 nicklas 67   /**
4130 26 Sep 16 nicklas 68     Adds a comment to the script.
4130 26 Sep 16 nicklas 69     @param comment The comment (must not contain any new lines)
4130 26 Sep 16 nicklas 70   */
4130 26 Sep 16 nicklas 71   public void comment(String comment)
4130 26 Sep 16 nicklas 72   {
5996 26 Aug 20 nicklas 73     script.append(indentString).append("# ").append(comment).append("\n");
4130 26 Sep 16 nicklas 74   }
4130 26 Sep 16 nicklas 75   
4130 26 Sep 16 nicklas 76   /**
4130 26 Sep 16 nicklas 77     Adds an empty line.
4130 26 Sep 16 nicklas 78   */
4130 26 Sep 16 nicklas 79   public void newLine()
4130 26 Sep 16 nicklas 80   {
4130 26 Sep 16 nicklas 81     script.append("\n");
4130 26 Sep 16 nicklas 82   }
4130 26 Sep 16 nicklas 83   
4130 26 Sep 16 nicklas 84   /**
4229 10 Nov 16 nicklas 85     Append the current time (hour:minute:second) and comment into the 'time.log' file in the
4130 26 Sep 16 nicklas 86     log folder. Eg: '12:23:45 Demux complete'.
4130 26 Sep 16 nicklas 87   */
4130 26 Sep 16 nicklas 88   public void time(String comment)
4130 26 Sep 16 nicklas 89   {
5996 26 Aug 20 nicklas 90     script.append(indentString).append("echo \"`date +%H:%M:%S` ").append(comment).append("\" >> ").append(logFolder).append("/time.log\n");
4130 26 Sep 16 nicklas 91   }
4130 26 Sep 16 nicklas 92   
4130 26 Sep 16 nicklas 93   /**
4130 26 Sep 16 nicklas 94     Update the progress file. The existing file will be replaced.
4130 26 Sep 16 nicklas 95     @param progress The progress (in percent)
4130 26 Sep 16 nicklas 96     @param status The status message
4130 26 Sep 16 nicklas 97   */
4130 26 Sep 16 nicklas 98   public void progress(int progress, String status)
4130 26 Sep 16 nicklas 99   {
5996 26 Aug 20 nicklas 100     script.append(indentString).append("echo ").append(progress).append(" \"").append(status).append("\" > ").append(logFolder).append("/progress\n");
4130 26 Sep 16 nicklas 101   }
4130 26 Sep 16 nicklas 102   
4130 26 Sep 16 nicklas 103   /**
6625 04 Mar 22 nicklas 104     Add an exported variable declaration to the script:
6625 04 Mar 22 nicklas 105     export key="value". The value is surrounded with ". 
6625 04 Mar 22 nicklas 106     Nothing is added if the value is null.
6625 04 Mar 22 nicklas 107     @since 1.5
6625 04 Mar 22 nicklas 108   */
6625 04 Mar 22 nicklas 109   public void export(String key, String value)
6625 04 Mar 22 nicklas 110   {
6625 04 Mar 22 nicklas 111     if (value != null) cmd("export "+key+"=\""+value+"\"");
6625 04 Mar 22 nicklas 112   }
6625 04 Mar 22 nicklas 113   
6625 04 Mar 22 nicklas 114   
6625 04 Mar 22 nicklas 115   /**
6625 04 Mar 22 nicklas 116     Add an variable declaration to the script: key="value". 
6625 04 Mar 22 nicklas 117     The value is surrounded with ". Nothing is added if the value is null.
6625 04 Mar 22 nicklas 118     @since 1.5
6625 04 Mar 22 nicklas 119   */
6625 04 Mar 22 nicklas 120   public void var(String key, String value)
6625 04 Mar 22 nicklas 121   {
6625 04 Mar 22 nicklas 122     if (value != null) cmd(key+"=\""+value+"\"");
6625 04 Mar 22 nicklas 123   }
6625 04 Mar 22 nicklas 124
6625 04 Mar 22 nicklas 125   
6625 04 Mar 22 nicklas 126   /**
4130 26 Sep 16 nicklas 127     Run a command.
4130 26 Sep 16 nicklas 128   */
4130 26 Sep 16 nicklas 129   public void cmd(String cmd)
4130 26 Sep 16 nicklas 130   {
6659 01 Apr 22 nicklas 131     if (cmd == null) return;
5996 26 Aug 20 nicklas 132     checkIndent(cmd, autoUnindent, -1);
5996 26 Aug 20 nicklas 133     script.append(indentString).append(cmd).append("\n");
5996 26 Aug 20 nicklas 134     checkIndent(cmd, autoIndent, 1);
4130 26 Sep 16 nicklas 135   }
4130 26 Sep 16 nicklas 136   
4130 26 Sep 16 nicklas 137   /**
5996 26 Aug 20 nicklas 138     If cmd starts with one of the given keys, apply delta to the
5996 26 Aug 20 nicklas 139     indentation level and create a new indent-string.
5996 26 Aug 20 nicklas 140   */
5996 26 Aug 20 nicklas 141   private void checkIndent(String cmd, Collection<String> keys, int delta)
5996 26 Aug 20 nicklas 142   {
5996 26 Aug 20 nicklas 143     for (String k : keys)
5996 26 Aug 20 nicklas 144     {
5996 26 Aug 20 nicklas 145       if (cmd.startsWith(k)) 
5996 26 Aug 20 nicklas 146       {
5996 26 Aug 20 nicklas 147         indentLevel = Math.max(0, indentLevel+delta);
5996 26 Aug 20 nicklas 148         indentString = " ".repeat(indentLevel*3);
5996 26 Aug 20 nicklas 149         return;
5996 26 Aug 20 nicklas 150       }
5996 26 Aug 20 nicklas 151     }
5996 26 Aug 20 nicklas 152   }
5996 26 Aug 20 nicklas 153   
5996 26 Aug 20 nicklas 154   /**
4130 26 Sep 16 nicklas 155     Echo the given text to stdout
4130 26 Sep 16 nicklas 156   */
4130 26 Sep 16 nicklas 157   public void echo(String text)
4130 26 Sep 16 nicklas 158   {
6659 01 Apr 22 nicklas 159     if (text == null) return;
4130 26 Sep 16 nicklas 160     cmd("echo " + text);
4130 26 Sep 16 nicklas 161   }
4130 26 Sep 16 nicklas 162   
4130 26 Sep 16 nicklas 163   /**
4130 26 Sep 16 nicklas 164     Adds a command that is executed in the background. The return value is the
4130 26 Sep 16 nicklas 165     name of a variable containing the process id of the background task. Use this
4130 26 Sep 16 nicklas 166     in {@link #waitForProcess(String)} to add a wait statement that also catches the
4130 26 Sep 16 nicklas 167     exit code of the background task.
4130 26 Sep 16 nicklas 168   */
4130 26 Sep 16 nicklas 169   public String bkgr(String cmd)
4130 26 Sep 16 nicklas 170   {
4130 26 Sep 16 nicklas 171     if (bgProcesses == null) bgProcesses = new HashMap<String, String>();
4130 26 Sep 16 nicklas 172     String pid = "BG"+nextBgPid;
4130 26 Sep 16 nicklas 173     nextBgPid++;
4130 26 Sep 16 nicklas 174     bgProcesses.put(pid, cmd);
5996 26 Aug 20 nicklas 175     script.append(indentString).append(cmd).append(" &\n");
5996 26 Aug 20 nicklas 176     script.append(indentString).append(pid).append("=$!\n");
4130 26 Sep 16 nicklas 177     return pid;
4130 26 Sep 16 nicklas 178   }
4130 26 Sep 16 nicklas 179   
4130 26 Sep 16 nicklas 180   /**
4130 26 Sep 16 nicklas 181     Adds a batch of commands that are executed in the background.
4130 26 Sep 16 nicklas 182     Note that it is only the batch as a whole that is executed in 
4130 26 Sep 16 nicklas 183     the background, the individual commands are executed in serial.
4130 26 Sep 16 nicklas 184     The return value is the name of a variable containing the process 
4130 26 Sep 16 nicklas 185     id of the background task. Use this in {@link #waitForProcess(String)} to add 
4130 26 Sep 16 nicklas 186     a wait statement that also catches the exit code of the background task.
4130 26 Sep 16 nicklas 187   */
4130 26 Sep 16 nicklas 188   public String bkgr(String... cmds)
4130 26 Sep 16 nicklas 189   {
4130 26 Sep 16 nicklas 190     StringBuilder cmd = new StringBuilder();
4130 26 Sep 16 nicklas 191     cmd.append("{ ");
4130 26 Sep 16 nicklas 192     for (String c : cmds)
4130 26 Sep 16 nicklas 193     {
4130 26 Sep 16 nicklas 194       cmd.append(c).append("; ");
4130 26 Sep 16 nicklas 195     }
4130 26 Sep 16 nicklas 196     cmd.append(" }");
4130 26 Sep 16 nicklas 197     return bkgr(cmd.toString());
4130 26 Sep 16 nicklas 198   }
4130 26 Sep 16 nicklas 199
4130 26 Sep 16 nicklas 200   /**
4130 26 Sep 16 nicklas 201     Wait for the given process to end.
4130 26 Sep 16 nicklas 202     @param pid The process id variable name returned by {@link #bkgr(String)}.
4130 26 Sep 16 nicklas 203     @throws IllegalArgumentException If the pid is unknown
4130 26 Sep 16 nicklas 204   */
4130 26 Sep 16 nicklas 205   public void waitForProcess(String pid)
4130 26 Sep 16 nicklas 206   {
4130 26 Sep 16 nicklas 207     if (bgProcesses == null || !bgProcesses.containsKey(pid))
4130 26 Sep 16 nicklas 208     {
4130 26 Sep 16 nicklas 209       throw new IllegalArgumentException("Unknown background process id: " + pid);
4130 26 Sep 16 nicklas 210     }
4130 26 Sep 16 nicklas 211     bgProcesses.remove(pid);
5996 26 Aug 20 nicklas 212     script.append(indentString).append("wait $").append(pid).append("\n");
4130 26 Sep 16 nicklas 213   }
4130 26 Sep 16 nicklas 214
4130 26 Sep 16 nicklas 215   /**
4130 26 Sep 16 nicklas 216     Get the current length of the script.
4130 26 Sep 16 nicklas 217   */
4130 26 Sep 16 nicklas 218   public int length()
4130 26 Sep 16 nicklas 219   {
4130 26 Sep 16 nicklas 220     return script.length();
4130 26 Sep 16 nicklas 221   }
4130 26 Sep 16 nicklas 222   
4130 26 Sep 16 nicklas 223   /**
4130 26 Sep 16 nicklas 224     Get the generated script.
4130 26 Sep 16 nicklas 225      
4130 26 Sep 16 nicklas 226     @throws IllegalStateException If {@link #waitForProcess(String)} hasn't 
4130 26 Sep 16 nicklas 227       been called for all commands started in the background ({@link #bkgr(String)} 
4130 26 Sep 16 nicklas 228       or {@link #bkgr(String...)})
4130 26 Sep 16 nicklas 229   */
4130 26 Sep 16 nicklas 230   @Override
4130 26 Sep 16 nicklas 231   public String toString() 
4130 26 Sep 16 nicklas 232   {
4130 26 Sep 16 nicklas 233     if (bgProcesses != null && bgProcesses.size() > 0)
4130 26 Sep 16 nicklas 234     {
4130 26 Sep 16 nicklas 235       throw new IllegalStateException("The script start background tasks, but does not wait for them: " + bgProcesses);
4130 26 Sep 16 nicklas 236     }
4130 26 Sep 16 nicklas 237     return script.toString();
4130 26 Sep 16 nicklas 238   }
4130 26 Sep 16 nicklas 239   
6627 07 Mar 22 nicklas 240   /**
6627 07 Mar 22 nicklas 241     Get an upload source representation for uploading the script
6627 07 Mar 22 nicklas 242     to a remote server.
6627 07 Mar 22 nicklas 243     @param name The name to give to the file on the remote system
6627 07 Mar 22 nicklas 244     @since 1.5
6627 07 Mar 22 nicklas 245   */
6627 07 Mar 22 nicklas 246   public UploadSource toUploadSource(String name)
6627 07 Mar 22 nicklas 247   {
6627 07 Mar 22 nicklas 248     return new StringUploadSource(name, toString());
6627 07 Mar 22 nicklas 249   }
4130 26 Sep 16 nicklas 250   
4130 26 Sep 16 nicklas 251 }