1# Copyright 2001-2004 Brad Chapman.
2# Revisions copyright 2009-2013 by Peter Cock.
3# All rights reserved.
4#
5# This file is part of the Biopython distribution and governed by your
6# choice of the "Biopython License Agreement" or the "BSD 3-Clause License".
7# Please see the LICENSE file that should have been included as part of this
8# package.
9"""General mechanisms to access applications in Biopython (OBSOLETE).
10
11This module is not intended for direct use. It provides the basic objects which
12are subclassed by our command line wrappers, such as:
13
14 - Bio.Align.Applications
15 - Bio.Blast.Applications
16 - Bio.Emboss.Applications
17 - Bio.Sequencing.Applications
18
19These modules provide wrapper classes for command line tools to help you
20construct command line strings by setting the values of each parameter.
21The finished command line strings are then normally invoked via the built-in
22Python module subprocess.
23
24Due to the on going maintainance burden or keeping command line application
25wrappers up to date, we have decided to deprecate and eventually remove them.
26We instead now recommend building your command line and invoking it directly
27with the subprocess module.
28"""
29import os
30import platform
31import sys
32import subprocess
33import re
34
35
36# Use this regular expression to test the property names are going to
37# be valid as Python properties or arguments
38_re_prop_name = re.compile(r"^[a-zA-Z][a-zA-Z0-9_]*$")
39assert _re_prop_name.match("t")
40assert _re_prop_name.match("test")
41assert _re_prop_name.match("_test") is None  # we don't want private names
42assert _re_prop_name.match("-test") is None
43assert _re_prop_name.match("any-hyphen") is None
44assert _re_prop_name.match("underscore_ok")
45assert _re_prop_name.match("test_name")
46assert _re_prop_name.match("test2")
47# These are reserved names in Python itself,
48_reserved_names = [
49    "and",
50    "del",
51    "from",
52    "not",
53    "while",
54    "as",
55    "elif",
56    "global",
57    "or",
58    "with",
59    "assert",
60    "else",
61    "if",
62    "pass",
63    "yield",
64    "break",
65    "except",
66    "import",
67    "print",
68    "class",
69    "exec",
70    "in",
71    "raise",
72    "continue",
73    "finally",
74    "is",
75    "return",
76    "def",
77    "for",
78    "lambda",
79    "try",
80]
81# These are reserved names due to the way the wrappers work
82_local_reserved_names = ["set_parameter"]
83
84
85class ApplicationError(subprocess.CalledProcessError):
86    """Raised when an application returns a non-zero exit status (OBSOLETE).
87
88    The exit status will be stored in the returncode attribute, similarly
89    the command line string used in the cmd attribute, and (if captured)
90    stdout and stderr as strings.
91
92    This exception is a subclass of subprocess.CalledProcessError.
93
94    >>> err = ApplicationError(-11, "helloworld", "", "Some error text")
95    >>> err.returncode, err.cmd, err.stdout, err.stderr
96    (-11, 'helloworld', '', 'Some error text')
97    >>> print(err)
98    Non-zero return code -11 from 'helloworld', message 'Some error text'
99
100    """
101
102    def __init__(self, returncode, cmd, stdout="", stderr=""):
103        """Initialize the class."""
104        self.returncode = returncode
105        self.cmd = cmd
106        self.stdout = stdout
107        self.stderr = stderr
108
109    def __str__(self):
110        """Format the error as a string."""
111        # get first line of any stderr message
112        try:
113            msg = self.stderr.lstrip().split("\n", 1)[0].rstrip()
114        except Exception:  # TODO, ValueError? AttributeError?
115            msg = ""
116        if msg:
117            return "Non-zero return code %d from %r, message %r" % (
118                self.returncode,
119                self.cmd,
120                msg,
121            )
122        else:
123            return "Non-zero return code %d from %r" % (self.returncode, self.cmd)
124
125    def __repr__(self):
126        """Represent the error as a string."""
127        return "ApplicationError(%i, %s, %s, %s)" % (
128            self.returncode,
129            self.cmd,
130            self.stdout,
131            self.stderr,
132        )
133
134
135class AbstractCommandline:
136    r"""Generic interface for constructing command line strings (OBSOLETE).
137
138    This class shouldn't be called directly; it should be subclassed to
139    provide an implementation for a specific application.
140
141    For a usage example we'll show one of the EMBOSS wrappers.  You can set
142    options when creating the wrapper object using keyword arguments - or
143    later using their corresponding properties:
144
145    >>> from Bio.Emboss.Applications import WaterCommandline
146    >>> cline = WaterCommandline(gapopen=10, gapextend=0.5)
147    >>> cline
148    WaterCommandline(cmd='water', gapopen=10, gapextend=0.5)
149
150    You can instead manipulate the parameters via their properties, e.g.
151
152    >>> cline.gapopen
153    10
154    >>> cline.gapopen = 20
155    >>> cline
156    WaterCommandline(cmd='water', gapopen=20, gapextend=0.5)
157
158    You can clear a parameter you have already added by 'deleting' the
159    corresponding property:
160
161    >>> del cline.gapopen
162    >>> cline.gapopen
163    >>> cline
164    WaterCommandline(cmd='water', gapextend=0.5)
165
166    Once you have set the parameters you need, you can turn the object into
167    a string (e.g. to log the command):
168
169    >>> str(cline)
170    Traceback (most recent call last):
171    ...
172    ValueError: You must either set outfile (output filename), or enable filter or stdout (output to stdout).
173
174    In this case the wrapper knows certain arguments are required to construct
175    a valid command line for the tool.  For a complete example,
176
177    >>> from Bio.Emboss.Applications import WaterCommandline
178    >>> water_cmd = WaterCommandline(gapopen=10, gapextend=0.5)
179    >>> water_cmd.asequence = "asis:ACCCGGGCGCGGT"
180    >>> water_cmd.bsequence = "asis:ACCCGAGCGCGGT"
181    >>> water_cmd.outfile = "temp_water.txt"
182    >>> print(water_cmd)
183    water -outfile=temp_water.txt -asequence=asis:ACCCGGGCGCGGT -bsequence=asis:ACCCGAGCGCGGT -gapopen=10 -gapextend=0.5
184    >>> water_cmd
185    WaterCommandline(cmd='water', outfile='temp_water.txt', asequence='asis:ACCCGGGCGCGGT', bsequence='asis:ACCCGAGCGCGGT', gapopen=10, gapextend=0.5)
186
187    You would typically run the command line via a standard Python operating
188    system call using the subprocess module for full control. For the simple
189    case where you just want to run the command and get the output:
190
191    stdout, stderr = water_cmd()
192
193    Note that by default we assume the underlying tool is installed on the
194    system $PATH environment variable. This is normal under Linux/Unix, but
195    may need to be done manually under Windows. Alternatively, you can specify
196    the full path to the binary as the first argument (cmd):
197
198    >>> from Bio.Emboss.Applications import WaterCommandline
199    >>> water_cmd = WaterCommandline(r"C:\Program Files\EMBOSS\water.exe",
200    ...                              gapopen=10, gapextend=0.5,
201    ...                              asequence="asis:ACCCGGGCGCGGT",
202    ...                              bsequence="asis:ACCCGAGCGCGGT",
203    ...                              outfile="temp_water.txt")
204    >>> print(water_cmd)
205    "C:\Program Files\EMBOSS\water.exe" -outfile=temp_water.txt -asequence=asis:ACCCGGGCGCGGT -bsequence=asis:ACCCGAGCGCGGT -gapopen=10 -gapextend=0.5
206
207    Notice that since the path name includes a space it has automatically
208    been quoted.
209
210    """
211
212    # TODO - Replace the above example since EMBOSS doesn't work properly
213    # if installed into a folder with a space like "C:\Program Files\EMBOSS"
214    #
215    # Note the call example above is not a doctest as we can't handle EMBOSS
216    # (or any other tool) being missing in the unit tests.
217
218    parameters = None  # will be a list defined in subclasses
219
220    def __init__(self, cmd, **kwargs):
221        """Create a new instance of a command line wrapper object."""
222        # Init method - should be subclassed!
223        #
224        # The subclass methods should look like this:
225        #
226        # def __init__(self, cmd="muscle", **kwargs):
227        #     self.parameters = [...]
228        #     AbstractCommandline.__init__(self, cmd, **kwargs)
229        #
230        # i.e. There should have an optional argument "cmd" to set the location
231        # of the executable (with a sensible default which should work if the
232        # command is on the path on Unix), and keyword arguments.  It should
233        # then define a list of parameters, all objects derived from the base
234        # class _AbstractParameter.
235        #
236        # The keyword arguments should be any valid parameter name, and will
237        # be used to set the associated parameter.
238        self.program_name = cmd
239        try:
240            parameters = self.parameters
241        except AttributeError:
242            raise AttributeError(
243                "Subclass should have defined self.parameters"
244            ) from None
245        # Create properties for each parameter at run time
246        aliases = set()
247        for p in parameters:
248            if not p.names:
249                if not isinstance(p, _StaticArgument):
250                    raise TypeError("Expected %r to be of type _StaticArgument" % p)
251                continue
252            for name in p.names:
253                if name in aliases:
254                    raise ValueError("Parameter alias %s multiply defined" % name)
255                aliases.add(name)
256            name = p.names[-1]
257            if _re_prop_name.match(name) is None:
258                raise ValueError(
259                    "Final parameter name %r cannot be used as "
260                    "an argument or property name in python" % name
261                )
262            if name in _reserved_names:
263                raise ValueError(
264                    "Final parameter name %r cannot be used as "
265                    "an argument or property name because it is "
266                    "a reserved word in python" % name
267                )
268            if name in _local_reserved_names:
269                raise ValueError(
270                    "Final parameter name %r cannot be used as "
271                    "an argument or property name due to the "
272                    "way the AbstractCommandline class works" % name
273                )
274
275            # Beware of binding-versus-assignment confusion issues
276            def getter(name):
277                return lambda x: x._get_parameter(name)
278
279            def setter(name):
280                return lambda x, value: x.set_parameter(name, value)
281
282            def deleter(name):
283                return lambda x: x._clear_parameter(name)
284
285            doc = p.description
286            if isinstance(p, _Switch):
287                doc += (
288                    "\n\nThis property controls the addition of the %s "
289                    "switch, treat this property as a boolean." % p.names[0]
290                )
291            else:
292                doc += (
293                    "\n\nThis controls the addition of the %s parameter "
294                    "and its associated value.  Set this property to the "
295                    "argument value required." % p.names[0]
296                )
297            prop = property(getter(name), setter(name), deleter(name), doc)
298            setattr(self.__class__, name, prop)  # magic!
299        for key, value in kwargs.items():
300            self.set_parameter(key, value)
301
302    def _validate(self):
303        """Make sure the required parameters have been set (PRIVATE).
304
305        No return value - it either works or raises a ValueError.
306
307        This is a separate method (called from __str__) so that subclasses may
308        override it.
309        """
310        for p in self.parameters:
311            # Check for missing required parameters:
312            if p.is_required and not (p.is_set):
313                raise ValueError("Parameter %s is not set." % p.names[-1])
314            # Also repeat the parameter validation here, just in case?
315
316    def __str__(self):
317        """Make the commandline string with the currently set options.
318
319        e.g.
320
321        >>> from Bio.Emboss.Applications import WaterCommandline
322        >>> cline = WaterCommandline(gapopen=10, gapextend=0.5)
323        >>> cline.asequence = "asis:ACCCGGGCGCGGT"
324        >>> cline.bsequence = "asis:ACCCGAGCGCGGT"
325        >>> cline.outfile = "temp_water.txt"
326        >>> print(cline)
327        water -outfile=temp_water.txt -asequence=asis:ACCCGGGCGCGGT -bsequence=asis:ACCCGAGCGCGGT -gapopen=10 -gapextend=0.5
328        >>> str(cline)
329        'water -outfile=temp_water.txt -asequence=asis:ACCCGGGCGCGGT -bsequence=asis:ACCCGAGCGCGGT -gapopen=10 -gapextend=0.5'
330        """
331        self._validate()
332        commandline = "%s " % _escape_filename(self.program_name)
333        for parameter in self.parameters:
334            if parameter.is_set:
335                # This will include a trailing space:
336                commandline += str(parameter)
337        return commandline.strip()  # remove trailing space
338
339    def __repr__(self):
340        """Return a representation of the command line object for debugging.
341
342        e.g.
343
344        >>> from Bio.Emboss.Applications import WaterCommandline
345        >>> cline = WaterCommandline(gapopen=10, gapextend=0.5)
346        >>> cline.asequence = "asis:ACCCGGGCGCGGT"
347        >>> cline.bsequence = "asis:ACCCGAGCGCGGT"
348        >>> cline.outfile = "temp_water.txt"
349        >>> print(cline)
350        water -outfile=temp_water.txt -asequence=asis:ACCCGGGCGCGGT -bsequence=asis:ACCCGAGCGCGGT -gapopen=10 -gapextend=0.5
351        >>> cline
352        WaterCommandline(cmd='water', outfile='temp_water.txt', asequence='asis:ACCCGGGCGCGGT', bsequence='asis:ACCCGAGCGCGGT', gapopen=10, gapextend=0.5)
353        """
354        answer = "%s(cmd=%r" % (self.__class__.__name__, self.program_name)
355        for parameter in self.parameters:
356            if parameter.is_set:
357                if isinstance(parameter, _Switch):
358                    answer += ", %s=True" % parameter.names[-1]
359                else:
360                    answer += ", %s=%r" % (parameter.names[-1], parameter.value)
361        answer += ")"
362        return answer
363
364    def _get_parameter(self, name):
365        """Get a commandline option value (PRIVATE)."""
366        for parameter in self.parameters:
367            if name in parameter.names:
368                if isinstance(parameter, _Switch):
369                    return parameter.is_set
370                else:
371                    return parameter.value
372        raise ValueError("Option name %s was not found." % name)
373
374    def _clear_parameter(self, name):
375        """Reset or clear a commandline option value (PRIVATE)."""
376        cleared_option = False
377        for parameter in self.parameters:
378            if name in parameter.names:
379                parameter.value = None
380                parameter.is_set = False
381                cleared_option = True
382        if not cleared_option:
383            raise ValueError("Option name %s was not found." % name)
384
385    def set_parameter(self, name, value=None):
386        """Set a commandline option for a program (OBSOLETE).
387
388        Every parameter is available via a property and as a named
389        keyword when creating the instance. Using either of these is
390        preferred to this legacy set_parameter method which is now
391        OBSOLETE, and likely to be DEPRECATED and later REMOVED in
392        future releases.
393        """
394        set_option = False
395        for parameter in self.parameters:
396            if name in parameter.names:
397                if isinstance(parameter, _Switch):
398                    if value is None:
399                        import warnings
400
401                        warnings.warn(
402                            "For a switch type argument like %s, "
403                            "we expect a boolean.  None is treated "
404                            "as FALSE!" % parameter.names[-1]
405                        )
406                    parameter.is_set = bool(value)
407                    set_option = True
408                else:
409                    if value is not None:
410                        self._check_value(value, name, parameter.checker_function)
411                        parameter.value = value
412                    parameter.is_set = True
413                    set_option = True
414        if not set_option:
415            raise ValueError("Option name %s was not found." % name)
416
417    def _check_value(self, value, name, check_function):
418        """Check whether the given value is valid (PRIVATE).
419
420        No return value - it either works or raises a ValueError.
421
422        This uses the passed function 'check_function', which can either
423        return a [0, 1] (bad, good) value or raise an error. Either way
424        this function will raise an error if the value is not valid, or
425        finish silently otherwise.
426        """
427        if check_function is not None:
428            is_good = check_function(value)  # May raise an exception
429            if is_good not in [0, 1, True, False]:
430                raise ValueError(
431                    "Result of check_function: %r is of an unexpected value" % is_good
432                )
433            if not is_good:
434                raise ValueError(
435                    "Invalid parameter value %r for parameter %s" % (value, name)
436                )
437
438    def __setattr__(self, name, value):
439        """Set attribute name to value (PRIVATE).
440
441        This code implements a workaround for a user interface issue.
442        Without this __setattr__ attribute-based assignment of parameters
443        will silently accept invalid parameters, leading to known instances
444        of the user assuming that parameters for the application are set,
445        when they are not.
446
447        >>> from Bio.Emboss.Applications import WaterCommandline
448        >>> cline = WaterCommandline(gapopen=10, gapextend=0.5, stdout=True)
449        >>> cline.asequence = "a.fasta"
450        >>> cline.bsequence = "b.fasta"
451        >>> cline.csequence = "c.fasta"
452        Traceback (most recent call last):
453        ...
454        ValueError: Option name csequence was not found.
455        >>> print(cline)
456        water -stdout -asequence=a.fasta -bsequence=b.fasta -gapopen=10 -gapextend=0.5
457
458        This workaround uses a whitelist of object attributes, and sets the
459        object attribute list as normal, for these.  Other attributes are
460        assumed to be parameters, and passed to the self.set_parameter method
461        for validation and assignment.
462        """
463        if name in ["parameters", "program_name"]:  # Allowed attributes
464            self.__dict__[name] = value
465        else:
466            self.set_parameter(name, value)  # treat as a parameter
467
468    def __call__(self, stdin=None, stdout=True, stderr=True, cwd=None, env=None):
469        """Execute command, wait for it to finish, return (stdout, stderr).
470
471        Runs the command line tool and waits for it to finish. If it returns
472        a non-zero error level, an exception is raised. Otherwise two strings
473        are returned containing stdout and stderr.
474
475        The optional stdin argument should be a string of data which will be
476        passed to the tool as standard input.
477
478        The optional stdout and stderr argument may be filenames (string),
479        but otherwise are treated as a booleans, and control if the output
480        should be captured as strings (True, default), or ignored by sending
481        it to /dev/null to avoid wasting memory (False). If sent to a file
482        or ignored, then empty string(s) are returned.
483
484        The optional cwd argument is a string giving the working directory
485        to run the command from. See Python's subprocess module documentation
486        for more details.
487
488        The optional env argument is a dictionary setting the environment
489        variables to be used in the new process. By default the current
490        process' environment variables are used. See Python's subprocess
491        module documentation for more details.
492
493        Default example usage::
494
495            from Bio.Emboss.Applications import WaterCommandline
496            water_cmd = WaterCommandline(gapopen=10, gapextend=0.5,
497                                         stdout=True, auto=True,
498                                         asequence="a.fasta", bsequence="b.fasta")
499            print("About to run: %s" % water_cmd)
500            std_output, err_output = water_cmd()
501
502        This functionality is similar to subprocess.check_output(). In general
503        if you require more control over running the command, use subprocess
504        directly.
505
506        When the program called returns a non-zero error level, a custom
507        ApplicationError exception is raised. This includes any stdout and
508        stderr strings captured as attributes of the exception object, since
509        they may be useful for diagnosing what went wrong.
510        """
511        if not stdout:
512            stdout_arg = open(os.devnull, "w")
513        elif isinstance(stdout, str):
514            stdout_arg = open(stdout, "w")
515        else:
516            stdout_arg = subprocess.PIPE
517
518        if not stderr:
519            stderr_arg = open(os.devnull, "w")
520        elif isinstance(stderr, str):
521            if stdout == stderr:
522                stderr_arg = stdout_arg  # Write both to the same file
523            else:
524                stderr_arg = open(stderr, "w")
525        else:
526            stderr_arg = subprocess.PIPE
527
528        # We may not need to supply any piped input, but we setup the
529        # standard input pipe anyway as a work around for a python
530        # bug if this is called from a Windows GUI program.  For
531        # details, see http://bugs.python.org/issue1124861
532        #
533        # Using universal newlines is important on Python 3, this
534        # gives unicode handles rather than bytes handles.
535
536        # Windows 7, 8, 8.1 and 10 want shell = True
537        if sys.platform != "win32":
538            use_shell = True
539        else:
540            win_ver = platform.win32_ver()[0]
541            if win_ver in ["7", "8", "post2012Server", "10"]:
542                use_shell = True
543            else:
544                use_shell = False
545        child_process = subprocess.Popen(
546            str(self),
547            stdin=subprocess.PIPE,
548            stdout=stdout_arg,
549            stderr=stderr_arg,
550            universal_newlines=True,
551            cwd=cwd,
552            env=env,
553            shell=use_shell,
554        )
555        # Use .communicate as can get deadlocks with .wait(), see Bug 2804
556        stdout_str, stderr_str = child_process.communicate(stdin)
557        if not stdout:
558            assert not stdout_str, stdout_str
559        if not stderr:
560            assert not stderr_str, stderr_str
561        return_code = child_process.returncode
562
563        # Particularly important to close handles on Jython and PyPy
564        # (where garbage collection is less predictable) and on Windows
565        # (where cannot delete files with an open handle):
566        if not stdout or isinstance(stdout, str):
567            # We opened /dev/null or a file
568            stdout_arg.close()
569        if not stderr or (isinstance(stderr, str) and stdout != stderr):
570            # We opened /dev/null or a file
571            stderr_arg.close()
572
573        if return_code:
574            raise ApplicationError(return_code, str(self), stdout_str, stderr_str)
575        return stdout_str, stderr_str
576
577
578class _AbstractParameter:
579    """A class to hold information about a parameter for a commandline.
580
581    Do not use this directly, instead use one of the subclasses.
582    """
583
584    def __init__(self):
585        raise NotImplementedError
586
587    def __str__(self):
588        raise NotImplementedError
589
590
591class _Option(_AbstractParameter):
592    """Represent an option that can be set for a program.
593
594    This holds UNIXish options like --append=yes and -a yes,
595    where a value (here "yes") is generally expected.
596
597    For UNIXish options like -kimura in clustalw which don't
598    take a value, use the _Switch object instead.
599
600    Attributes:
601     - names -- a list of string names (typically two entries) by which
602       the parameter can be set via the legacy set_parameter method
603       (eg ["-a", "--append", "append"]). The first name in list is used
604       when building the command line. The last name in the list is a
605       "human readable" name describing the option in one word. This
606       must be a valid Python identifier as it is used as the property
607       name and as a keyword argument, and should therefore follow PEP8
608       naming.
609     - description -- a description of the option. This is used as
610       the property docstring.
611     - filename -- True if this argument is a filename (or other argument
612       that should be quoted) and should be automatically quoted if it
613       contains spaces.
614     - checker_function -- a reference to a function that will determine
615       if a given value is valid for this parameter. This function can either
616       raise an error when given a bad value, or return a [0, 1] decision on
617       whether the value is correct.
618     - equate -- should an equals sign be inserted if a value is used?
619     - is_required -- a flag to indicate if the parameter must be set for
620       the program to be run.
621     - is_set -- if the parameter has been set
622     - value -- the value of a parameter
623
624    """
625
626    def __init__(
627        self,
628        names,
629        description,
630        filename=False,
631        checker_function=None,
632        is_required=False,
633        equate=True,
634    ):
635        self.names = names
636        if not isinstance(description, str):
637            raise TypeError("Should be a string: %r for %s" % (description, names[-1]))
638        # Note 'filename' is for any string with spaces that needs quoting
639        self.is_filename = filename
640        self.checker_function = checker_function
641        self.description = description
642        self.equate = equate
643        self.is_required = is_required
644
645        self.is_set = False
646        self.value = None
647
648    def __str__(self):
649        """Return the value of this option for the commandline.
650
651        Includes a trailing space.
652        """
653        # Note: Before equate was handled explicitly, the old
654        # code would do either "--name " or "--name=value ",
655        # or " -name " or " -name value ".  This choice is now
656        # now made explicitly when setting up the option.
657        if self.value is None:
658            return "%s " % self.names[0]
659        if self.is_filename:
660            v = _escape_filename(self.value)
661        else:
662            v = str(self.value)
663        if self.equate:
664            return "%s=%s " % (self.names[0], v)
665        else:
666            return "%s %s " % (self.names[0], v)
667
668
669class _Switch(_AbstractParameter):
670    """Represent an optional argument switch for a program.
671
672    This holds UNIXish options like -kimura in clustalw which don't
673    take a value, they are either included in the command string
674    or omitted.
675
676    Attributes:
677     - names -- a list of string names (typically two entries) by which
678       the parameter can be set via the legacy set_parameter method
679       (eg ["-a", "--append", "append"]). The first name in list is used
680       when building the command line. The last name in the list is a
681       "human readable" name describing the option in one word. This
682       must be a valid Python identifier as it is used as the property
683       name and as a keyword argument, and should therefore follow PEP8
684       naming.
685     - description -- a description of the option. This is used as
686       the property docstring.
687     - is_set -- if the parameter has been set
688
689    NOTE - There is no value attribute, see is_set instead,
690
691    """
692
693    def __init__(self, names, description):
694        self.names = names
695        self.description = description
696        self.is_set = False
697        self.is_required = False
698
699    def __str__(self):
700        """Return the value of this option for the commandline.
701
702        Includes a trailing space.
703        """
704        assert not hasattr(self, "value")
705        if self.is_set:
706            return "%s " % self.names[0]
707        else:
708            return ""
709
710
711class _Argument(_AbstractParameter):
712    """Represent an argument on a commandline.
713
714    The names argument should be a list containing one string.
715    This must be a valid Python identifier as it is used as the
716    property name and as a keyword argument, and should therefore
717    follow PEP8 naming.
718    """
719
720    def __init__(
721        self,
722        names,
723        description,
724        filename=False,
725        checker_function=None,
726        is_required=False,
727    ):
728        # if len(names) != 1:
729        #    raise ValueError("The names argument to _Argument should be a "
730        #                     "single entry list with a PEP8 property name.")
731        self.names = names
732        if not isinstance(description, str):
733            raise TypeError("Should be a string: %r for %s" % (description, names[-1]))
734        # Note 'filename' is for any string with spaces that needs quoting
735        self.is_filename = filename
736        self.checker_function = checker_function
737        self.description = description
738        self.is_required = is_required
739        self.is_set = False
740        self.value = None
741
742    def __str__(self):
743        if self.value is None:
744            return " "
745        elif self.is_filename:
746            return "%s " % _escape_filename(self.value)
747        else:
748            return "%s " % self.value
749
750
751class _ArgumentList(_Argument):
752    """Represent a variable list of arguments on a command line, e.g. multiple filenames."""
753
754    # TODO - Option to require at least one value? e.g. min/max count?
755
756    def __str__(self):
757        if not isinstance(self.value, list):
758            raise TypeError("Arguments should be a list")
759        if not self.value:
760            raise ValueError("Requires at least one filename")
761        # A trailing space is required so that parameters following the last filename
762        # do not appear merged.
763        # e.g.:  samtools cat in1.bam in2.bam-o out.sam  [without trailing space][Incorrect]
764        #        samtools cat in1.bam in2.bam -o out.sam  [with trailing space][Correct]
765        if self.is_filename:
766            return " ".join(_escape_filename(v) for v in self.value) + " "
767        else:
768            return " ".join(self.value) + " "
769
770
771class _StaticArgument(_AbstractParameter):
772    """Represent a static (read only) argument on a commandline.
773
774    This is not intended to be exposed as a named argument or
775    property of a command line wrapper object.
776    """
777
778    def __init__(self, value):
779        self.names = []
780        self.is_required = False
781        self.is_set = True
782        self.value = value
783
784    def __str__(self):
785        return "%s " % self.value
786
787
788def _escape_filename(filename):
789    """Escape filenames with spaces by adding quotes (PRIVATE).
790
791    Note this will not add quotes if they are already included:
792
793    >>> print((_escape_filename('example with spaces')))
794    "example with spaces"
795    >>> print((_escape_filename('"example with spaces"')))
796    "example with spaces"
797    >>> print((_escape_filename(1)))
798    1
799
800    Note the function is more generic than the name suggests, since it
801    is used to add quotes around any string arguments containing spaces.
802    """
803    # Is adding the following helpful
804    # if os.path.isfile(filename):
805    #    # On Windows, if the file exists, we can ask for
806    #    # its alternative short name (DOS style 8.3 format)
807    #    # which has no spaces in it.  Note that this name
808    #    # is not portable between machines, or even folder!
809    #    try:
810    #        import win32api
811    #        short = win32api.GetShortPathName(filename)
812    #        assert os.path.isfile(short)
813    #        return short
814    #    except ImportError:
815    #        pass
816    if not isinstance(filename, str):
817        # for example the NCBI BLAST+ -outfmt argument can be an integer
818        return filename
819    if " " not in filename:
820        return filename
821    # We'll just quote it - works on Windows, Mac OS X etc
822    if filename.startswith('"') and filename.endswith('"'):
823        # Its already quoted
824        return filename
825    else:
826        return '"%s"' % filename
827
828
829def _test():
830    """Run the Bio.Application module's doctests (PRIVATE)."""
831    import doctest
832
833    doctest.testmod(verbose=1)
834
835
836if __name__ == "__main__":
837    # Run the doctests
838    _test()
839