JAL-4420 Allow preprocessing of arguments if they are all simply filenames (or URLs...
authorBen Soares <b.soares@dundee.ac.uk>
Fri, 17 May 2024 14:55:52 +0000 (15:55 +0100)
committerBen Soares <b.soares@dundee.ac.uk>
Fri, 17 May 2024 14:55:52 +0000 (15:55 +0100)
src/jalview/bin/argparser/ArgParser.java
src/jalview/util/ArgParserUtils.java [new file with mode: 0644]
src/jalview/util/FileUtils.java

index 12e1b1d..bbcf831 100644 (file)
@@ -33,11 +33,13 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import jalview.bin.Cache;
 import jalview.bin.Console;
 import jalview.bin.Jalview;
 import jalview.bin.Jalview.ExitCode;
 import jalview.bin.argparser.Arg.Opt;
 import jalview.bin.argparser.Arg.Type;
+import jalview.util.ArgParserUtils;
 import jalview.util.FileUtils;
 import jalview.util.HttpUtils;
 
@@ -317,6 +319,12 @@ public class ArgParser
       return;
     }
 
+    // preprocess for associated files only if no actual --args supplied
+    if (!dd && !Cache.getDefault("NOARGPREPROCESSING", false))
+    {
+      ArgParserUtils.preProcessArgs(args);
+    }
+
     if (bsa != null)
     {
       this.bootstrapArgs = bsa;
diff --git a/src/jalview/util/ArgParserUtils.java b/src/jalview/util/ArgParserUtils.java
new file mode 100644 (file)
index 0000000..be0f428
--- /dev/null
@@ -0,0 +1,171 @@
+package jalview.util;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import jalview.bin.Cache;
+import jalview.bin.argparser.Arg;
+import jalview.io.FileFormatI;
+import jalview.io.FileFormats;
+
+public class ArgParserUtils
+{
+  private static Set<String> alignmentExtensions = null;
+
+  private static Set<String> annotationsExtensions = null;
+
+  private static Set<String> featuresExtensions = null;
+
+  private static Set<String> treeExtensions = null;
+
+  public static void preProcessArgs(List<String> filenames)
+  {
+    // Running through the arguments to look for '-arg' or '--arg' should
+    // already have happened, not doing it again.
+    if (alignmentExtensions == null)
+    {
+      setValidExtensions();
+    }
+
+    Set<String> filesSet = new HashSet<>(filenames);
+
+    List<Arg> argSet = new ArrayList<>();
+    argSet.add(Arg.ANNOTATIONS);
+    argSet.add(Arg.FEATURES);
+    argSet.add(Arg.TREE);
+
+    Map<Arg, Set<String>> argExtensionsMap = new HashMap<>();
+    argExtensionsMap.put(Arg.ANNOTATIONS, annotationsExtensions);
+    argExtensionsMap.put(Arg.FEATURES, featuresExtensions);
+    argExtensionsMap.put(Arg.TREE, treeExtensions);
+
+    Map<String, BaseInfo> baseInfoMap = new HashMap<>();
+
+    // we make a copy to run through, and delete associated filenames from the
+    // original
+    final List<String> filenamesCopy = new ArrayList<>(filenames);
+    for (String filename : filenamesCopy)
+    {
+      if (filename == null)
+      {
+        continue;
+      }
+      String ext = FileUtils.getExtension(filename);
+      if (ext != null && ext.length() > 0
+              && alignmentExtensions.contains(ext))
+      {
+        BaseInfo bi = new BaseInfo(filename);
+
+        // this includes the dot at the end of the basename
+        String base = FileUtils.getBase(filename);
+
+        for (Arg arg : argSet)
+        {
+          for (String possibleExt : argExtensionsMap.get(arg))
+          {
+            String possibleFile = base + possibleExt;
+            if (filesSet.contains(possibleFile))
+            {
+              bi.putAssociatedFile(arg, possibleFile);
+              filenames.remove(possibleFile);
+              break;
+            }
+          }
+        }
+
+        baseInfoMap.put(filename, bi);
+      }
+    }
+
+    // now we go through the saved associated files and add them back in to the
+    // right places with the appropriate argument
+    for (String filename : baseInfoMap.keySet())
+    {
+      BaseInfo bi = baseInfoMap.get(filename);
+
+      int pos = filenames.indexOf(filename);
+      if (bi.associatedFiles != null)
+      {
+        for (Arg a : bi.associatedFiles.keySet())
+        {
+          String associatedFile = bi.associatedFiles.get(a);
+          if (associatedFile == null)
+          {
+            // shouldn't happen!
+            continue;
+          }
+          filenames.add(pos + 1, a.argString());
+          filenames.add(pos + 2, associatedFile);
+        }
+      }
+      // add an --open arg to separate from other files
+      filenames.add(pos, Arg.OPEN.argString());
+    }
+  }
+
+  private static void setValidExtensions()
+  {
+    alignmentExtensions = new HashSet<>();
+    FileFormats ffs = FileFormats.getInstance();
+    List<String> validFormats = ffs.getReadableFormats();
+
+    for (String fname : validFormats)
+    {
+      FileFormatI tff = ffs.forName(fname);
+      String[] extensions = tff.getExtensions().split(",");
+      for (String ext : extensions)
+      {
+        alignmentExtensions.add(ext.toLowerCase(Locale.ROOT));
+      }
+    }
+
+    annotationsExtensions = new HashSet<>();
+    for (String ext : Cache
+            .getDefault("ARGPREPROCESSORANNOTATIONSEXTENSIONS",
+                    "annotation,annotations")
+            .split(","))
+    {
+      annotationsExtensions.add(ext);
+    }
+
+    featuresExtensions = new HashSet<>();
+    for (String ext : Cache.getDefault("ARGPREPROCESSORFEATURESEXTENSIONS",
+            "feature,features").split(","))
+    {
+      featuresExtensions.add(ext);
+    }
+
+    treeExtensions = new HashSet<>();
+    for (String ext : Cache.getDefault("ARGPREPROCESSORTREEEXTENSIONS",
+            "tree,tre,newick,nwk").split(","))
+    {
+      treeExtensions.add(ext);
+    }
+  }
+}
+
+class BaseInfo
+{
+  String filename;
+
+  Map<Arg, String> associatedFiles = null;
+
+  BaseInfo(String filename)
+  {
+    this.filename = filename;
+  }
+
+  void putAssociatedFile(Arg a, String file)
+  {
+    if (associatedFiles == null)
+    {
+      associatedFiles = new HashMap<>();
+    }
+    associatedFiles.put(a, file);
+  }
+}
index e7d274c..f2883a7 100644 (file)
@@ -22,6 +22,8 @@ package jalview.util;
 
 import java.io.File;
 import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
 import java.nio.file.FileSystems;
 import java.nio.file.FileVisitOption;
 import java.nio.file.FileVisitResult;
@@ -291,4 +293,72 @@ public class FileUtils
 
     return parentDir.mkdirs();
   }
+
+  /**
+   * get a guessed file extension from a String only
+   * 
+   * @param String
+   *          filename
+   * @return String extension
+   */
+  public static String getExtension(String filename)
+  {
+    return getBaseOrExtension(filename, true);
+  }
+
+  /**
+   * getBase returns everything in a path/URI up to (and including) an extension
+   * dot. Note this is not the same as getBasename() since getBasename() only
+   * gives the filename base, not the path too. If no extension dot is found
+   * (i.e. a dot in character position 2 or more of the filename (after the last
+   * slash) then the whole path is considered the base.
+   * 
+   * @param filename
+   * @return String base
+   */
+  public static String getBase(String filename)
+  {
+    return getBaseOrExtension(filename, false);
+  }
+
+  public static String getBaseOrExtension(String filename0,
+          boolean extension)
+  {
+    if (filename0 == null)
+    {
+      return null;
+    }
+    String filename = filename0;
+    boolean isUrl = false;
+    if (HttpUtils.startsWithHttpOrHttps(filename))
+    {
+      try
+      {
+        URL url = new URL(filename);
+        filename = url.getPath();
+        isUrl = true;
+      } catch (MalformedURLException e)
+      {
+        // continue to treat as a filename
+      }
+    }
+    int dot = filename.lastIndexOf('.');
+    int slash = filename.lastIndexOf('/');
+    if (!File.separator.equals("/") && !isUrl)
+    {
+      slash = filename.lastIndexOf(File.separator);
+    }
+    // only the dot of the filename (not dots in path) and not if it's a .hidden
+    // file
+    boolean hasExtension = dot > slash + 1;
+    if (extension)
+    {
+      return hasExtension ? filename.substring(dot + 1) : null;
+    }
+    else
+    {
+      dot = filename0.lastIndexOf('.');
+      return hasExtension ? filename0.substring(0, dot + 1) : filename0;
+    }
+  }
 }