Clover icon

Coverage Report

  1. Project Clover database Mon Nov 18 2024 09:38:20 GMT
  2. Package jalview.bin.argparser

File ArgParser.java

 

Coverage histogram

../../../img/srcFileCovDistChart8.png
13% of files have more coverage

Code metrics

226
404
35
2
1,315
941
209
0.52
11.54
17.5
5.97

Classes

Class Line # Actions
ArgParser 46 404 209
0.732330873.2%
ArgParser.Op 1105 0 0
-1.0 -
 

Contributing tests

This file is covered by 79 tests. .

Source view

1    /*
2    * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3    * Copyright (C) $$Year-Rel$$ The Jalview Authors
4    *
5    * This file is part of Jalview.
6    *
7    * Jalview is free software: you can redistribute it and/or
8    * modify it under the terms of the GNU General Public License
9    * as published by the Free Software Foundation, either version 3
10    * of the License, or (at your option) any later version.
11    *
12    * Jalview is distributed in the hope that it will be useful, but
13    * WITHOUT ANY WARRANTY; without even the implied warranty
14    * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15    * PURPOSE. See the GNU General Public License for more details.
16    *
17    * You should have received a copy of the GNU General Public License
18    * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19    * The Jalview Authors are detailed in the 'AUTHORS' file.
20    */
21    package jalview.bin.argparser;
22   
23    import java.io.File;
24    import java.io.IOException;
25    import java.nio.file.Files;
26    import java.nio.file.Paths;
27    import java.util.ArrayList;
28    import java.util.Arrays;
29    import java.util.Collections;
30    import java.util.EnumSet;
31    import java.util.Enumeration;
32    import java.util.HashMap;
33    import java.util.List;
34    import java.util.Map;
35   
36    import jalview.bin.Cache;
37    import jalview.bin.Console;
38    import jalview.bin.Jalview;
39    import jalview.bin.Jalview.ExitCode;
40    import jalview.bin.argparser.Arg.Opt;
41    import jalview.bin.argparser.Arg.Type;
42    import jalview.util.ArgParserUtils;
43    import jalview.util.FileUtils;
44    import jalview.util.HttpUtils;
45   
 
46    public class ArgParser
47    {
48    protected static final String SINGLEDASH = "-";
49   
50    protected static final String DOUBLEDASH = "--";
51   
52    public static final char EQUALS = '=';
53   
54    public static final String STDOUTFILENAME = "-";
55   
56    protected static final String NEGATESTRING = "no";
57   
58    /**
59    * the default linked id prefix used for no id (ie when not even square braces
60    * are provided)
61    */
62    protected static final String DEFAULTLINKEDIDPREFIX = "JALVIEW:";
63   
64    /**
65    * the linkedId string used to match all linkedIds seen so far
66    */
67    protected static final String MATCHALLLINKEDIDS = "*";
68   
69    /**
70    * the linkedId string used to match all of the last --open'ed linkedIds
71    */
72    protected static final String MATCHOPENEDLINKEDIDS = "open*";
73   
74    /**
75    * the counter added to the default linked id prefix
76    */
77    private int defaultLinkedIdCounter = 0;
78   
79    /**
80    * the substitution string used to use the defaultLinkedIdCounter
81    */
82    private static final String DEFAULTLINKEDIDCOUNTER = "{}";
83   
84    /**
85    * the linked id prefix used for --open files. NOW the same as DEFAULT
86    */
87    protected static final String OPENLINKEDIDPREFIX = DEFAULTLINKEDIDPREFIX;
88   
89    /**
90    * the counter used for {n} substitutions
91    */
92    private int linkedIdAutoCounter = 0;
93   
94    /**
95    * the linked id substitution string used to increment the idCounter (and use
96    * the incremented value)
97    */
98    private static final String INCREMENTLINKEDIDAUTOCOUNTER = "{++n}";
99   
100    /**
101    * the linked id substitution string used to use the idCounter
102    */
103    private static final String LINKEDIDAUTOCOUNTER = "{n}";
104   
105    /**
106    * the linked id substitution string used to use the filename extension of
107    * --append or --open
108    */
109    private static final String LINKEDIDEXTENSION = "{extension}";
110   
111    /**
112    * the linked id substitution string used to use the base filename of --append
113    */
114    /** or --open */
115    private static final String LINKEDIDBASENAME = "{basename}";
116   
117    /**
118    * the linked id substitution string used to use the dir path of --append or
119    * --open
120    */
121    private static final String LINKEDIDDIRNAME = "{dirname}";
122   
123    /**
124    * On-the-fly substitution (not made at argument parsing time)! the current
125    * structure filename extension
126    */
127    private static final String STRUCTUREEXTENSION = "{structureextension}";
128   
129    /**
130    * On-the-fly substitution (not made at argument parsing time)! the current
131    * structure filename base
132    */
133    private static final String STRUCTUREBASENAME = "{structurebasename}";
134   
135    /**
136    * On-the-fly substitution (not made at argument parsing time)! the current
137    * structure filename dir path
138    */
139    private static final String STRUCTUREDIRNAME = "{structuredirname}";
140   
141    /**
142    * On-the-fly substitution (not made at argument parsing time)! increment the
143    * on-the-fly counter and substitute the incremented value
144    */
145    private static final String INCREMENTONTHEFLYCOUNTER = "{++m}";
146   
147    /**
148    * On-the-fly substitution (not made at argument parsing time)! the current
149    * substitute with the on-the-fly counter
150    */
151    private static final String ONTHEFLYCOUNTER = "{m}";
152   
153    /**
154    * the string used for on-the-fly structure filename substitutions
155    */
156    private String currentStructureFilename = null;
157   
158    /**
159    * the counter used for on-the-fly {m} substitutions
160    */
161    private int ontheflyCounter = 0;
162   
163    /**
164    * the current argfile
165    */
166    private String argFile = null;
167   
168    /**
169    * the linked id substitution string used to use the dir path of the latest
170    */
171    /** --argfile name */
172    private static final String ARGFILEBASENAME = "{argfilebasename}";
173   
174    /**
175    * the linked id substitution string used to use the dir path of the latest
176    * --argfile name
177    */
178    private static final String ARGFILEDIRNAME = "{argfiledirname}";
179   
180    /**
181    * flag to say whether {n} subtitutions in output filenames should be made.
182    * Turn on and off with --substitutions and --nosubstitutions Start with it on
183    */
184    private boolean substitutions = true;
185   
186    /**
187    * flag to say whether the default linkedId is the current default linked id
188    *
189    * or ALL linkedIds
190    */
191    private boolean allLinkedIds = false;
192   
193    /**
194    * flag to say whether the structure arguments should be applied to all
195    * structures with this linked id
196    */
197    private boolean allStructures = false;
198   
199    protected static final Map<String, Arg> argMap;
200   
201    protected Map<String, ArgValuesMap> linkedArgs = new HashMap<>();
202   
203    protected List<String> linkedOrder = new ArrayList<>();
204   
205    protected List<String> storedLinkedIds = new ArrayList<>();
206   
207    protected List<Arg> argList = new ArrayList<>();
208   
209    private static final char ARGFILECOMMENT = '#';
210   
211    private int argIndex = 0;
212   
213    private BootstrapArgs bootstrapArgs = null;
214   
215    private boolean oldArguments = false;
216   
217    private boolean mixedArguments = false;
218   
219    /**
220    * saved examples of mixed arguments
221    */
222    private String[] mixedExamples = new String[] { null, null };
223   
 
224  6 toggle static
225    {
226  6 argMap = new HashMap<>();
227  6 for (Arg a : EnumSet.allOf(Arg.class))
228    {
229  408 for (String argName : a.getNames())
230    {
231  456 if (argMap.containsKey(argName))
232    {
233  0 Console.warn("Trying to add argument name multiple times: '"
234    + argName + "'");
235  0 if (argMap.get(argName) != a)
236    {
237  0 Console.error(
238    "Trying to add argument name multiple times for different Args: '"
239    + argMap.get(argName).getName() + ":" + argName
240    + "' and '" + a.getName() + ":" + argName
241    + "'");
242    }
243  0 continue;
244    }
245  456 argMap.put(argName, a);
246    }
247    }
248    }
249   
 
250  46 toggle public ArgParser(String[] args)
251    {
252  46 this(args, false, null);
253    }
254   
 
255  91 toggle public ArgParser(String[] args, boolean initsubstitutions,
256    BootstrapArgs bsa)
257    {
258    /*
259    * Make a mutable new ArrayList so that shell globbing parser works.
260    * (When shell file globbing is used, there are a sequence of non-Arg
261    * arguments (which are the expanded globbed filenames) that need to be
262    * consumed by the --append/--argfile/etc Arg which is most easily done
263    * by removing these filenames from the list one at a time. This can't be
264    * done with an ArrayList made with only Arrays.asList(String[] args) as
265    * that is not mutable. )
266    */
267  91 this(new ArrayList<>(Arrays.asList(args)), initsubstitutions, false,
268    bsa);
269    }
270   
 
271  0 toggle public ArgParser(List<String> args, boolean initsubstitutions)
272    {
273  0 this(args, initsubstitutions, false, null);
274    }
275   
 
276  94 toggle public ArgParser(List<String> args, boolean initsubstitutions,
277    boolean allowPrivate, BootstrapArgs bsa)
278    {
279    // do nothing if there are no "--" args and (some "-" args || >0 arg is
280    // "open")
281  94 boolean d = false;
282  94 boolean dd = false;
283  94 for (String arg : args)
284    {
285  429 if (arg.startsWith(DOUBLEDASH))
286    {
287  391 dd = true;
288  391 if (mixedExamples[1] == null)
289    {
290  87 mixedExamples[1] = arg;
291    }
292    }
293  38 else if ((arg.startsWith("-") && !arg.equals(STDOUTFILENAME))
294    || arg.equals("open"))
295    {
296  6 d = true;
297  6 if (mixedExamples[0] == null)
298    {
299  1 mixedExamples[0] = arg;
300    }
301    }
302    }
303  94 if (d)
304    {
305  1 if (dd)
306    {
307  1 mixedArguments = true;
308    }
309    else
310    {
311  0 oldArguments = true;
312    }
313    }
314   
315  94 if (oldArguments || mixedArguments)
316    {
317    // leave it to the old style -- parse an empty list
318  1 parse(new ArrayList<String>(), false, false);
319  1 return;
320    }
321   
322    // preprocess for associated files only if no actual --args supplied
323  93 Console.debug("Supplied args are " + args);
324  93 if (!dd && !Cache.getDefault("NOARGPREPROCESSING", false))
325    {
326  7 ArgParserUtils.preProcessArgs(args);
327  7 Console.debug("Preprocessed args are " + args);
328    }
329   
330  93 if (bsa != null)
331    {
332  47 this.bootstrapArgs = bsa;
333    }
334    else
335    {
336  46 this.bootstrapArgs = BootstrapArgs.getBootstrapArgs(args);
337    }
338  93 parse(args, initsubstitutions, allowPrivate);
339    }
340   
 
341  94 toggle private void parse(List<String> args, boolean initsubstitutions,
342    boolean allowPrivate)
343    {
344  94 this.substitutions = initsubstitutions;
345   
346    /*
347    * If the first argument does not start with "--" or "-" or is not "open",
348    * and is a filename that exists or a URL, it is probably a file/list of
349    * files to open so we insert an Arg.OPEN argument before it. This will
350    * mean the list of files at the start of the arguments are all opened
351    * separately.
352    */
353  94 if (args.size() > 0)
354    {
355  86 String arg0 = args.get(0);
356  86 if (arg0 != null
357    && (!arg0.startsWith(DOUBLEDASH) && !arg0.startsWith("-")
358    && !arg0.equals("open") && (new File(arg0).exists()
359    || HttpUtils.startsWithHttpOrHttps(arg0))))
360    {
361    // insert "--open" at the start
362  0 args.add(0, Arg.OPEN.argString());
363    }
364    }
365   
366  499 for (int i = 0; i < args.size(); i++)
367    {
368  405 String arg = args.get(i);
369   
370    // look for double-dash, e.g. --arg
371  405 if (arg.startsWith(DOUBLEDASH))
372    {
373  390 String argName = null;
374  390 String val = null;
375  390 List<String> globVals = null; // for Opt.GLOB only
376  390 SubVals globSubVals = null; // also for use by Opt.GLOB only
377  390 String linkedId = null;
378  390 String givenLinkedId = null; // this is preserved to add to each
379    // "ArgValue"
380  390 Type type = null;
381   
382    // look for equals e.g. --arg=value
383  390 int equalPos = arg.indexOf(EQUALS);
384  390 if (equalPos > -1)
385    {
386  192 argName = arg.substring(DOUBLEDASH.length(), equalPos);
387  192 val = arg.substring(equalPos + 1);
388    }
389    else
390    {
391  198 argName = arg.substring(DOUBLEDASH.length());
392    }
393   
394    // look for linked ID e.g. --arg[linkedID]
395  390 int idOpen = argName.indexOf('[');
396  390 int idClose = argName.indexOf(']');
397  390 if (idOpen > -1 && idClose == argName.length() - 1)
398    {
399  57 linkedId = argName.substring(idOpen + 1, idClose);
400  57 givenLinkedId = linkedId;
401  57 argName = argName.substring(0, idOpen);
402    }
403   
404    // look for type modification e.g. --help-opening
405  390 int dashPos = argName.indexOf(SINGLEDASH);
406  390 if (dashPos > -1)
407    {
408  0 String potentialArgName = argName.substring(0, dashPos);
409  0 Arg potentialArg = argMap.get(potentialArgName);
410  0 if (potentialArg != null && potentialArg.hasOption(Opt.HASTYPE))
411    {
412  0 String typeName = argName.substring(dashPos + 1);
413  0 try
414    {
415  0 type = Type.valueOf(typeName);
416    } catch (IllegalArgumentException e)
417    {
418  0 type = Type.INVALID;
419    }
420  0 argName = argName.substring(0, dashPos);
421    }
422    }
423   
424  390 Arg a = argMap.get(argName);
425    // check for boolean prepended by "no" e.g. --nowrap
426  390 boolean negated = false;
427  390 if (a == null)
428    {
429  45 if (argName.startsWith(NEGATESTRING) && argMap
430    .containsKey(argName.substring(NEGATESTRING.length())))
431    {
432  45 argName = argName.substring(NEGATESTRING.length());
433  45 a = argMap.get(argName);
434  45 negated = true;
435    }
436    else
437    {
438    // after all other args, look for Opt.PREFIXKEV args if still not
439    // found
440  0 for (Arg potentialArg : EnumSet.allOf(Arg.class))
441    {
442  0 if (potentialArg.hasOption(Opt.PREFIXKEV) && argName != null
443    && argName.startsWith(potentialArg.getName())
444    && equalPos > -1)
445    {
446  0 val = argName.substring(potentialArg.getName().length())
447    + EQUALS + val;
448  0 argName = argName.substring(0,
449    potentialArg.getName().length());
450  0 a = potentialArg;
451  0 break;
452    }
453    }
454    }
455    }
456   
457    // check for config errors
458  390 if (a == null)
459    {
460    // arg not found
461  0 Console.error("Argument '" + arg + "' not recognised. Exiting.");
462  0 Jalview.exit(
463    "Invalid argument used." + System.lineSeparator() + "Use"
464    + System.lineSeparator() + "jalview "
465    + Arg.HELP.argString() + System.lineSeparator()
466    + "for a usage statement.",
467    ExitCode.INVALID_ARGUMENT);
468  0 continue;
469    }
470  390 if (a.hasOption(Opt.PRIVATE) && !allowPrivate)
471    {
472  0 Console.error(
473    "Argument '" + a.argString() + "' is private. Ignoring.");
474  0 continue;
475    }
476  390 if (!a.hasOption(Opt.BOOLEAN) && negated)
477    {
478    // used "no" with a non-boolean option
479  0 Console.error("Argument '" + DOUBLEDASH + NEGATESTRING + argName
480    + "' not a boolean option. Ignoring.");
481  0 continue;
482    }
483  390 if (!a.hasOption(Opt.STRING) && equalPos > -1)
484    {
485    // set --argname=value when arg does not accept values
486  0 Console.error("Argument '" + a.argString()
487    + "' does not expect a value (given as '" + arg
488    + "'). Ignoring.");
489  0 continue;
490    }
491  390 if (!a.hasOption(Opt.LINKED) && linkedId != null)
492    {
493    // set --argname[linkedId] when arg does not use linkedIds
494  0 Console.error("Argument '" + a.argString()
495    + "' does not expect a linked id (given as '" + arg
496    + "'). Ignoring.");
497  0 continue;
498    }
499   
500    // String value(s)
501  390 if (a.hasOption(Opt.STRING))
502    {
503  217 if (equalPos >= 0)
504    {
505  192 if (a.hasOption(Opt.GLOB))
506    {
507    // strip off and save the SubVals to be added individually later
508  81 globSubVals = new SubVals(val);
509    // make substitutions before looking for files
510  81 String fileGlob = makeSubstitutions(globSubVals.getContent(),
511    linkedId);
512  81 globVals = FileUtils.getFilenamesFromGlob(fileGlob);
513    }
514    else
515    {
516    // val is already set -- will be saved in the ArgValue later in
517    // the normal way
518    }
519    }
520    else
521    {
522    // There is no "=" so value is next arg or args (possibly shell
523    // glob-expanded)
524  25 if (i + 1 >= args.size())
525    {
526    // no value to take for arg, which wants a value
527  4 Console.error("Argument '" + a.getName()
528    + "' requires a value, none given. Ignoring.");
529  4 continue;
530    }
531    // deal with bash globs here (--arg val* is expanded before reaching
532    // the JVM). Note that SubVals cannot be used in this case.
533    // If using the --arg=val then the glob is preserved and Java globs
534    // will be used later. SubVals can be used.
535  21 if (a.hasOption(Opt.GLOB))
536    {
537    // if this is the first argument with a file list at the start of
538    // the args we add filenames from index i instead of i+1
539  6 globVals = getShellGlobbedFilenameValues(a, args, i + 1);
540    }
541    else
542    {
543  15 val = args.get(i + 1);
544    }
545    }
546    }
547   
548    // make NOACTION adjustments
549    // default and auto counter increments
550  386 if (a == Arg.NPP)
551    {
552  0 linkedIdAutoCounter++;
553    }
554  386 else if (a == Arg.SUBSTITUTIONS)
555    {
556  27 substitutions = !negated;
557    }
558  359 else if (a == Arg.SETARGFILE)
559    {
560  7 argFile = val;
561    }
562  352 else if (a == Arg.UNSETARGFILE)
563    {
564  7 argFile = null;
565    }
566  345 else if (a == Arg.ALL)
567    {
568  11 allLinkedIds = !negated;
569    }
570  334 else if (a == Arg.ALLSTRUCTURES)
571    {
572  0 allStructures = !negated;
573    }
574   
575  386 if (a.hasOption(Opt.STORED))
576    {
577    // reset the lastOpenedLinkedIds list
578  38 this.storedLinkedIds = new ArrayList<>();
579    }
580   
581    // this is probably only Arg.NEW and Arg.OPEN
582  386 if (a.hasOption(Opt.INCREMENTDEFAULTCOUNTER))
583    {
584    // use the next default prefixed OPENLINKEDID
585  55 defaultLinkedId(true);
586    }
587   
588  386 String autoCounterString = null;
589  386 String defaultLinkedId = defaultLinkedId(false);
590  386 boolean usingDefaultLinkedId = false;
591  386 if (a.hasOption(Opt.LINKED))
592    {
593  232 if (linkedId == null)
594    {
595  175 if (a.hasOption(Opt.OUTPUTFILE) && a.hasOption(Opt.ALLOWMULTIID)
596    && val.contains(MATCHALLLINKEDIDS))
597    {
598    // --output=*.ext is shorthand for --output {basename}.ext
599    // --output=*/*.ext is shorthand for
600    // --output {dirname}/{basename}.ext
601    // (or --image=*.ext)
602  3 linkedId = allLinkedIds ? MATCHALLLINKEDIDS
603    : MATCHOPENEDLINKEDIDS;
604  3 val = FileUtils.convertWildcardsToPath(val, MATCHALLLINKEDIDS,
605    LINKEDIDDIRNAME, LINKEDIDBASENAME);
606    }
607  172 else if (allLinkedIds && a.hasOption(Opt.ALLOWMULTIID))
608    {
609  20 linkedId = MATCHALLLINKEDIDS;
610    }
611  175 if (allLinkedIds)
612    {
613    // user has made conscious decision for these args to apply to
614    // all, so set givenLinkedId too
615  21 givenLinkedId = linkedId;
616    }
617  154 else if (a.hasOption(Opt.ALLOWMULTIID)
618    && this.storedLinkedIds != null
619    && this.storedLinkedIds.size() > 0)
620    {
621  62 linkedId = MATCHOPENEDLINKEDIDS;
622    }
623    else
624    {
625    // use default linkedId for linked arguments
626  92 linkedId = defaultLinkedId;
627  92 usingDefaultLinkedId = true;
628  92 Console.debug("Changing linkedId to '" + linkedId + "' from "
629    + arg);
630    }
631    }
632    else
633    {
634  57 if (linkedId.contains(LINKEDIDAUTOCOUNTER))
635    {
636    // turn {n} to the autoCounter
637  36 autoCounterString = Integer.toString(linkedIdAutoCounter);
638  36 linkedId = linkedId.replace(LINKEDIDAUTOCOUNTER,
639    autoCounterString);
640  36 Console.debug("Changing linkedId to '" + linkedId + "' from "
641    + arg);
642    }
643  57 if (linkedId.contains(INCREMENTLINKEDIDAUTOCOUNTER))
644    {
645    // turn {++n} to the incremented autoCounter
646  12 autoCounterString = Integer.toString(++linkedIdAutoCounter);
647  12 linkedId = linkedId.replace(INCREMENTLINKEDIDAUTOCOUNTER,
648    autoCounterString);
649  12 Console.debug("Changing linkedId to '" + linkedId + "' from "
650    + arg);
651    }
652    }
653    }
654   
655    // do not continue in this block for NOACTION args
656  386 if (a.hasOption(Opt.NOACTION))
657  78 continue;
658   
659  308 ArgValuesMap avm = getOrCreateLinkedArgValuesMap(linkedId);
660   
661    // not dealing with both NODUPLICATEVALUES and GLOB
662  308 if (a.hasOption(Opt.NODUPLICATEVALUES) && avm.hasValue(a, val))
663    {
664  0 Console.error("Argument '" + a.argString()
665    + "' cannot contain a duplicate value ('" + val
666    + "'). Ignoring this and subsequent occurrences.");
667  0 continue;
668    }
669   
670    // check for unique id
671  308 SubVals subvals = new SubVals(val);
672  308 boolean addNewSubVals = false;
673  308 String id = subvals.get(ArgValues.ID);
674  308 if (id != null && avm.hasId(a, id))
675    {
676  0 Console.error("Argument '" + a.argString()
677    + "' has a duplicate id ('" + id + "'). Ignoring.");
678  0 continue;
679    }
680   
681    // set allstructures to all non-primary structure options in this linked
682    // id if --allstructures has been set
683  308 if (allStructures && (a.hasType(Type.STRUCTURE)
684    // || a.getType() == Type.STRUCTUREIMAGE)
685    ) && !a.hasOption(Opt.PRIMARY))
686    {
687  0 if (!subvals.has(Arg.ALLSTRUCTURES.getName()))
688    // && !subvals.has("structureid"))
689    {
690  0 subvals.put(Arg.ALLSTRUCTURES.getName(), "true");
691  0 addNewSubVals = true;
692    }
693    }
694   
695  308 ArgValues avs = avm.getOrCreateArgValues(a);
696   
697    // store appropriate String value(s)
698  308 if (a.hasOption(Opt.STRING))
699    {
700  206 if (a.hasOption(Opt.GLOB) && globVals != null
701    && globVals.size() > 0)
702    {
703  87 Enumeration<String> gve = Collections.enumeration(globVals);
704  244 while (gve.hasMoreElements())
705    {
706  157 String v = gve.nextElement();
707  157 SubVals vsv = new SubVals(globSubVals, v);
708  157 addValue(linkedId, givenLinkedId, type, avs, vsv, v,
709    argIndex++, true);
710    // if we're using defaultLinkedId and the arg increments the
711    // counter:
712  157 if (gve.hasMoreElements() && usingDefaultLinkedId
713    && a.hasOption(Opt.INCREMENTDEFAULTCOUNTER))
714    {
715    // increment the default linkedId
716  52 linkedId = defaultLinkedId(true);
717    // get new avm and avs
718  52 avm = linkedArgs.get(linkedId);
719  52 avs = avm.getOrCreateArgValues(a);
720    }
721    }
722    }
723    else
724    {
725    // addValue(linkedId, type, avs, val, argIndex, true);
726  119 addValue(linkedId, givenLinkedId, type, avs,
727  119 addNewSubVals ? subvals : null, val, argIndex, true);
728    }
729    }
730  102 else if (a.hasOption(Opt.BOOLEAN))
731    {
732  43 setBoolean(linkedId, givenLinkedId, type, avs, !negated,
733    argIndex);
734  43 setNegated(linkedId, avs, negated);
735    }
736  59 else if (a.hasOption(Opt.UNARY))
737    {
738  59 setBoolean(linkedId, givenLinkedId, type, avs, true, argIndex);
739    }
740   
741    // remove the '*' or 'open*' linkedId that should be empty if it was
742    // created
743  308 if ((MATCHALLLINKEDIDS.equals(linkedId)
744    || MATCHOPENEDLINKEDIDS.equals(linkedId))
745    && linkedArgs.containsKey(linkedId))
746    {
747  83 linkedArgs.remove(linkedId);
748    }
749    }
750    }
751    }
752   
 
753  480 toggle private void finaliseStoringArgValue(String linkedId, ArgValues avs)
754    {
755  480 Arg a = avs.arg();
756  480 incrementCount(linkedId, avs);
757  480 argIndex++;
758   
759    // store in appropriate place
760  480 if (a.hasOption(Opt.LINKED))
761    {
762    // store the order of linkedIds
763  376 if (!linkedOrder.contains(linkedId))
764  142 linkedOrder.add(linkedId);
765    }
766   
767    // store arg in the list of args used
768  480 if (!argList.contains(a))
769  218 argList.add(a);
770    }
771   
 
772  493 toggle private String defaultLinkedId(boolean increment)
773    {
774  493 String defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
775    .append(Integer.toString(defaultLinkedIdCounter)).toString();
776  493 if (increment)
777    {
778  197 while (linkedArgs.containsKey(defaultLinkedId))
779    {
780  90 defaultLinkedIdCounter++;
781  90 defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
782    .append(Integer.toString(defaultLinkedIdCounter))
783    .toString();
784    }
785    }
786  493 getOrCreateLinkedArgValuesMap(defaultLinkedId);
787  493 return defaultLinkedId;
788    }
789   
 
790  407 toggle public String makeSubstitutions(String val, String linkedId)
791    {
792  407 return makeSubstitutions(val, linkedId, false);
793    }
794   
 
795  407 toggle public String makeSubstitutions(String val, String linkedId,
796    boolean onthefly)
797    {
798  407 if (!this.substitutions || val == null)
799  185 return val;
800   
801  222 String subvals;
802  222 String rest;
803  222 if (val.indexOf('[') == 0 && val.indexOf(']') > 1)
804    {
805  0 int closeBracket = val.indexOf(']');
806  0 if (val.length() == closeBracket)
807  0 return val;
808  0 subvals = val.substring(0, closeBracket + 1);
809  0 rest = val.substring(closeBracket + 1);
810    }
811    else
812    {
813  222 subvals = "";
814  222 rest = val;
815    }
816  222 if (rest.contains(LINKEDIDAUTOCOUNTER))
817    {
818  14 rest = rest.replace(LINKEDIDAUTOCOUNTER,
819    String.valueOf(linkedIdAutoCounter));
820    }
821  222 if (rest.contains(INCREMENTLINKEDIDAUTOCOUNTER))
822    {
823  0 rest = rest.replace(INCREMENTLINKEDIDAUTOCOUNTER,
824    String.valueOf(++linkedIdAutoCounter));
825    }
826  222 if (rest.contains(DEFAULTLINKEDIDCOUNTER))
827    {
828  0 rest = rest.replace(DEFAULTLINKEDIDCOUNTER,
829    String.valueOf(defaultLinkedIdCounter));
830    }
831  222 ArgValuesMap avm = linkedArgs.get(linkedId);
832  222 if (avm != null)
833    {
834  215 if (rest.contains(LINKEDIDBASENAME))
835    {
836  74 rest = rest.replace(LINKEDIDBASENAME, avm.getBasename());
837    }
838  215 if (rest.contains(LINKEDIDEXTENSION))
839    {
840  0 rest = rest.replace(LINKEDIDEXTENSION, avm.getExtension());
841    }
842  215 if (rest.contains(LINKEDIDDIRNAME))
843    {
844  66 rest = rest.replace(LINKEDIDDIRNAME, avm.getDirname());
845    }
846    }
847  222 if (argFile != null)
848    {
849  40 if (rest.contains(ARGFILEBASENAME))
850    {
851  0 rest = rest.replace(ARGFILEBASENAME,
852    FileUtils.getBasename(new File(argFile)));
853    }
854  40 if (rest.contains(ARGFILEDIRNAME))
855    {
856  14 rest = rest.replace(ARGFILEDIRNAME,
857    FileUtils.getDirname(new File(argFile)));
858    }
859    }
860  222 if (onthefly)
861    {
862  0 if (rest.contains(ONTHEFLYCOUNTER))
863    {
864  0 rest = rest.replace(ONTHEFLYCOUNTER,
865    String.valueOf(ontheflyCounter));
866    }
867  0 if (rest.contains(INCREMENTONTHEFLYCOUNTER))
868    {
869  0 rest = rest.replace(INCREMENTONTHEFLYCOUNTER,
870    String.valueOf(++ontheflyCounter));
871    }
872  0 if (currentStructureFilename != null)
873    {
874  0 if (rest.contains(STRUCTUREBASENAME))
875    {
876  0 rest = rest.replace(STRUCTUREBASENAME, FileUtils
877    .getBasename(new File(currentStructureFilename)));
878    }
879  0 if (rest.contains(STRUCTUREDIRNAME))
880    {
881  0 rest = rest.replace(STRUCTUREDIRNAME,
882    FileUtils.getDirname(new File(currentStructureFilename)));
883    }
884    }
885    }
886   
887  222 return new StringBuilder(subvals).append(rest).toString();
888    }
889   
890    /*
891    * A helper method to take a list of String args where we're expecting
892    * {"--previousargs", "--arg", "file1", "file2", "file3", "--otheroptionsornot"}
893    * and the index of the globbed arg, here 1. It returns a List<String> {"file1",
894    * "file2", "file3"} *and remove these from the original list object* so that
895    * processing can continue from where it has left off, e.g. args has become
896    * {"--previousargs", "--arg", "--otheroptionsornot"} so the next increment
897    * carries on from the next --arg if available.
898    */
 
899  31 toggle protected static List<String> getShellGlobbedFilenameValues(Arg a,
900    List<String> args, int i)
901    {
902  31 List<String> vals = new ArrayList<>();
903  57 while (i < args.size() && !args.get(i).startsWith(DOUBLEDASH))
904    {
905  43 vals.add(FileUtils.substituteHomeDir(args.remove(i)));
906  43 if (!a.hasOption(Opt.GLOB))
907  17 break;
908    }
909  31 return vals;
910    }
911   
 
912  126 toggle public BootstrapArgs getBootstrapArgs()
913    {
914  126 return bootstrapArgs;
915    }
916   
 
917  47 toggle public boolean isSet(Arg a)
918    {
919  47 return a.hasOption(Opt.LINKED) ? isSetAtAll(a) : isSet(null, a);
920    }
921   
 
922  0 toggle public boolean isSetAtAll(Arg a)
923    {
924  0 for (String linkedId : linkedOrder)
925    {
926  0 if (isSet(linkedId, a))
927  0 return true;
928    }
929  0 return false;
930    }
931   
 
932  47 toggle public boolean isSet(String linkedId, Arg a)
933    {
934  47 ArgValuesMap avm = linkedArgs.get(linkedId);
935  47 return avm == null ? false : avm.containsArg(a);
936    }
937   
 
938  43 toggle public boolean getBoolean(Arg a)
939    {
940  43 if (!a.hasOption(Opt.BOOLEAN) && !a.hasOption(Opt.UNARY))
941    {
942  0 Console.warn("Getting boolean from non boolean Arg '" + a.getName()
943    + "'.");
944    }
945  43 return a.hasOption(Opt.LINKED) ? getBool("", a) : getBool(null, a);
946    }
947   
 
948  43 toggle public boolean getBool(String linkedId, Arg a)
949    {
950  43 ArgValuesMap avm = linkedArgs.get(linkedId);
951  43 if (avm == null)
952  10 return a.getDefaultBoolValue();
953  33 ArgValues avs = avm.getArgValues(a);
954  33 return avs == null ? a.getDefaultBoolValue() : avs.getBoolean();
955    }
956   
 
957  118 toggle public List<String> getLinkedIds()
958    {
959  118 return linkedOrder;
960    }
961   
 
962  410 toggle public ArgValuesMap getLinkedArgs(String id)
963    {
964  410 return linkedArgs.get(id);
965    }
966   
 
967  0 toggle @Override
968    public String toString()
969    {
970  0 StringBuilder sb = new StringBuilder();
971  0 sb.append("UNLINKED\n");
972  0 sb.append(argValuesMapToString(linkedArgs.get(null)));
973  0 if (getLinkedIds() != null)
974    {
975  0 sb.append("LINKED\n");
976  0 for (String id : getLinkedIds())
977    {
978    // already listed these as UNLINKED args
979  0 if (id == null)
980  0 continue;
981   
982  0 ArgValuesMap avm = getLinkedArgs(id);
983  0 sb.append("ID: '").append(id).append("'\n");
984  0 sb.append(argValuesMapToString(avm));
985    }
986    }
987  0 return sb.toString();
988    }
989   
 
990  0 toggle private static String argValuesMapToString(ArgValuesMap avm)
991    {
992  0 if (avm == null)
993  0 return null;
994  0 StringBuilder sb = new StringBuilder();
995  0 for (Arg a : avm.getArgKeys())
996    {
997  0 ArgValues v = avm.getArgValues(a);
998  0 sb.append(v.toString());
999  0 sb.append("\n");
1000    }
1001  0 return sb.toString();
1002    }
1003   
 
1004  3 toggle public static ArgParser parseArgFiles(List<String> argFilenameGlobs,
1005    boolean initsubstitutions, BootstrapArgs bsa)
1006    {
1007  3 List<File> argFiles = new ArrayList<>();
1008   
1009  3 for (String pattern : argFilenameGlobs)
1010    {
1011    // I don't think we want to dedup files, making life easier
1012  7 argFiles.addAll(FileUtils.getFilesFromGlob(pattern));
1013    }
1014   
1015  3 return parseArgFileList(argFiles, initsubstitutions, bsa);
1016    }
1017   
 
1018  3 toggle public static ArgParser parseArgFileList(List<File> argFiles,
1019    boolean initsubstitutions, BootstrapArgs bsa)
1020    {
1021  3 List<String> argsList = new ArrayList<>();
1022  3 for (File argFile : argFiles)
1023    {
1024  7 if (!argFile.exists())
1025    {
1026  0 String message = Arg.ARGFILE.argString() + EQUALS + "\""
1027    + argFile.getPath() + "\": File does not exist.";
1028  0 Jalview.exit(message, ExitCode.FILE_NOT_FOUND);
1029    }
1030  7 try
1031    {
1032  7 String setargfile = new StringBuilder(Arg.SETARGFILE.argString())
1033    .append(EQUALS).append(argFile.getCanonicalPath())
1034    .toString();
1035  7 argsList.add(setargfile);
1036  7 argsList.addAll(readArgFile(argFile));
1037  7 argsList.add(Arg.UNSETARGFILE.argString());
1038    } catch (IOException e)
1039    {
1040  0 String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
1041    + "\": File could not be read.";
1042  0 Jalview.exit(message, ExitCode.FILE_NOT_READABLE);
1043    }
1044    }
1045    // Third param "true" uses Opt.PRIVATE args --setargile=argfile and
1046    // --unsetargfile
1047  3 return new ArgParser(argsList, initsubstitutions, true, bsa);
1048    }
1049   
 
1050  53 toggle protected static List<String> readArgFile(File argFile)
1051    {
1052  53 List<String> args = new ArrayList<>();
1053  53 if (argFile != null && argFile.exists())
1054    {
1055  53 try
1056    {
1057  53 for (String line : Files.readAllLines(Paths.get(argFile.getPath())))
1058    {
1059  174 if (line != null && line.length() > 0
1060    && line.charAt(0) != ARGFILECOMMENT)
1061  174 args.add(line);
1062    }
1063    } catch (IOException e)
1064    {
1065  0 String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
1066    + "\": File could not be read.";
1067  0 Console.debug(message, e);
1068  0 Jalview.exit(message, ExitCode.FILE_NOT_READABLE);
1069    }
1070    }
1071  53 return args;
1072    }
1073   
1074    // the following methods look for the "*" linkedId and add the argvalue to all
1075    // linkedId ArgValues if it does.
1076    /**
1077    * This version inserts the subvals sv into all created values
1078    */
 
1079  276 toggle private void addValue(String linkedId, String givenLinkedId, Type type,
1080    ArgValues avs, SubVals sv, String v, int argIndex, boolean doSubs)
1081    {
1082  276 this.argValueOperation(Op.ADDVALUE, linkedId, givenLinkedId, type, avs,
1083    sv, v, false, argIndex, doSubs);
1084    }
1085   
 
1086  102 toggle private void setBoolean(String linkedId, String givenLinkedId, Type type,
1087    ArgValues avs, boolean b, int argIndex)
1088    {
1089  102 this.argValueOperation(Op.SETBOOLEAN, linkedId, givenLinkedId, type,
1090    avs, null, null, b, argIndex, false);
1091    }
1092   
 
1093  43 toggle private void setNegated(String linkedId, ArgValues avs, boolean b)
1094    {
1095  43 this.argValueOperation(Op.SETNEGATED, linkedId, null, null, avs, null,
1096    null, b, 0, false);
1097    }
1098   
 
1099  480 toggle private void incrementCount(String linkedId, ArgValues avs)
1100    {
1101  480 this.argValueOperation(Op.INCREMENTCOUNT, linkedId, null, null, avs,
1102    null, null, false, 0, false);
1103    }
1104   
 
1105    private enum Op
1106    {
1107    ADDVALUE, SETBOOLEAN, SETNEGATED, INCREMENTCOUNT
1108    }
1109   
 
1110  901 toggle private void argValueOperation(Op op, String linkedId,
1111    String givenLinkedId, Type type, ArgValues avs, SubVals sv,
1112    String v, boolean b, int argIndex, boolean doSubs)
1113    {
1114    // default to merge subvals if subvals are provided
1115  901 argValueOperation(op, linkedId, givenLinkedId, type, avs, sv, true, v,
1116    b, argIndex, doSubs);
1117    }
1118   
1119    /**
1120    * The following operations look for the "*" and "open*" linkedIds and add the
1121    * argvalue to all appropriate linkedId ArgValues if it does. If subvals are
1122    * supplied, they are inserted into all new set values.
1123    *
1124    * @param op
1125    * The ArgParser.Op operation
1126    * @param linkedId
1127    * The String linkedId from the ArgValuesMap
1128    * @param type
1129    * The Arg.Type to attach to this ArgValue
1130    * @param avs
1131    * The ArgValues for this linkedId
1132    * @param sv
1133    * Use these SubVals on the ArgValue
1134    * @param merge
1135    * Merge the SubVals with any existing on the value. False will
1136    * replace unless sv is null
1137    * @param v
1138    * The value of the ArgValue (may contain subvals).
1139    * @param b
1140    * The boolean value of the ArgValue.
1141    * @param argIndex
1142    * The argIndex for the ArgValue.
1143    * @param doSubs
1144    * Whether to perform substitutions on the subvals and value.
1145    */
 
1146  901 toggle private void argValueOperation(Op op, String linkedId,
1147    String givenLinkedId, Type type, ArgValues avs, SubVals sv,
1148    boolean merge, String v, boolean b, int argIndex, boolean doSubs)
1149    {
1150  901 Arg a = avs.arg();
1151   
1152  901 List<String> wildcardLinkedIds = null;
1153  901 if (a.hasOption(Opt.ALLOWMULTIID))
1154    {
1155  355 switch (linkedId)
1156    {
1157  21 case MATCHALLLINKEDIDS:
1158  21 wildcardLinkedIds = getLinkedIds();
1159  21 break;
1160  63 case MATCHOPENEDLINKEDIDS:
1161  63 wildcardLinkedIds = this.storedLinkedIds;
1162  63 break;
1163    }
1164    }
1165   
1166    // if we're not a wildcard linkedId and the arg is marked to be stored, add
1167    // to storedLinkedIds
1168  901 if (linkedId != null && wildcardLinkedIds == null
1169    && a.hasOption(Opt.STORED)
1170    && !storedLinkedIds.contains(linkedId))
1171    {
1172  90 storedLinkedIds.add(linkedId);
1173    }
1174   
1175    // if we are a wildcard linkedId, apply the arg and value to all appropriate
1176    // linkedIds
1177  901 if (wildcardLinkedIds != null)
1178    {
1179  84 for (String matchedLinkedId : wildcardLinkedIds)
1180    {
1181    // skip incorrectly stored wildcard ids!
1182  186 if (matchedLinkedId == null
1183    || MATCHALLLINKEDIDS.equals(matchedLinkedId)
1184    || MATCHOPENEDLINKEDIDS.equals(matchedLinkedId))
1185    {
1186  0 continue;
1187    }
1188  186 ArgValuesMap avm = linkedArgs.get(matchedLinkedId);
1189    // don't set an output if there isn't an input
1190  186 if (a.hasOption(Opt.REQUIREINPUT)
1191    && !avm.hasArgWithOption(Opt.INPUT))
1192  0 continue;
1193   
1194  186 ArgValues tavs = avm.getOrCreateArgValues(a);
1195  186 switch (op)
1196    {
1197   
1198  117 case ADDVALUE:
1199  117 String val = v;
1200  117 if (sv != null)
1201    {
1202  0 if (doSubs)
1203    {
1204  0 sv = new SubVals(sv, val, merge);
1205  0 val = makeSubstitutions(sv.getContent(), matchedLinkedId);
1206    }
1207  0 tavs.addValue(sv, type, val, argIndex, true, givenLinkedId);
1208    }
1209    else
1210    {
1211  117 if (doSubs)
1212    {
1213  117 val = makeSubstitutions(v, matchedLinkedId);
1214    }
1215  117 tavs.addValue(type, val, argIndex, true, givenLinkedId);
1216    }
1217  117 finaliseStoringArgValue(matchedLinkedId, tavs);
1218  117 break;
1219   
1220  68 case SETBOOLEAN:
1221  68 tavs.setBoolean(type, b, argIndex, true, givenLinkedId);
1222  68 finaliseStoringArgValue(matchedLinkedId, tavs);
1223  68 break;
1224   
1225  1 case SETNEGATED:
1226  1 tavs.setNegated(b, true);
1227  1 break;
1228   
1229  0 case INCREMENTCOUNT:
1230  0 tavs.incrementCount();
1231  0 break;
1232   
1233  0 default:
1234  0 break;
1235   
1236    }
1237   
1238    }
1239    }
1240    else // no wildcard linkedId -- do it simpler
1241    {
1242  817 switch (op)
1243    {
1244  209 case ADDVALUE:
1245  209 String val = v;
1246  209 if (sv != null)
1247    {
1248  157 if (doSubs)
1249    {
1250  157 val = makeSubstitutions(v, linkedId);
1251  157 sv = new SubVals(sv, val);
1252    }
1253  157 avs.addValue(sv, type, val, argIndex, false, givenLinkedId);
1254    }
1255    else
1256    {
1257  52 if (doSubs)
1258    {
1259  52 val = makeSubstitutions(v, linkedId);
1260    }
1261  52 avs.addValue(type, val, argIndex, false, givenLinkedId);
1262    }
1263  209 finaliseStoringArgValue(linkedId, avs);
1264  209 break;
1265   
1266  86 case SETBOOLEAN:
1267  86 avs.setBoolean(type, b, argIndex, false, givenLinkedId);
1268  86 finaliseStoringArgValue(linkedId, avs);
1269  86 break;
1270   
1271  42 case SETNEGATED:
1272  42 avs.setNegated(b, false);
1273  42 break;
1274   
1275  480 case INCREMENTCOUNT:
1276  480 avs.incrementCount();
1277  480 break;
1278   
1279  0 default:
1280  0 break;
1281    }
1282    }
1283    }
1284   
 
1285  801 toggle private ArgValuesMap getOrCreateLinkedArgValuesMap(String linkedId)
1286    {
1287  801 if (linkedArgs.containsKey(linkedId)
1288    && linkedArgs.get(linkedId) != null)
1289  459 return linkedArgs.get(linkedId);
1290   
1291  342 linkedArgs.put(linkedId, new ArgValuesMap(linkedId));
1292  342 return linkedArgs.get(linkedId);
1293    }
1294   
 
1295  43 toggle public boolean isOldStyle()
1296    {
1297  43 return oldArguments;
1298    }
1299   
 
1300  47 toggle public boolean isMixedStyle()
1301    {
1302  47 return mixedArguments;
1303    }
1304   
 
1305  0 toggle public String[] getMixedExamples()
1306    {
1307  0 return mixedExamples;
1308    }
1309   
 
1310  0 toggle public void setStructureFilename(String s)
1311    {
1312  0 this.currentStructureFilename = s;
1313    }
1314   
1315    }