1import os
2import platform
3import abc
4import inspect
5import hashlib
6import logging
7import cadocommand
8import cadologger
9import cadoparams
10
11
12class InspectType(type):
13    """ Meta-class that adds a class attribute "init_signature" with the
14    signature of the __init__() method, as produced by
15    inspect.getfullargspec()
16    """
17    def __init__(cls, name, bases, dct):
18        """
19        >>> def f(a:"A", b:"B"=1, *args:"*ARGS", c:"C", d:"D"=3, **kwargs:"**KWARGS"):
20        ...    pass
21        >>> i = inspect.getfullargspec(f)
22        >>> i.args
23        ['a', 'b']
24        >>> i.varargs
25        'args'
26        >>> i.varkw
27        'kwargs'
28        >>> i.defaults
29        (1,)
30        >>> i.kwonlyargs
31        ['c', 'd']
32        >>> i.kwonlydefaults
33        {'d': 3}
34        >>> i.annotations == {'a': 'A', 'b': 'B', 'c': 'C', 'd': 'D', 'args': '*ARGS', 'kwargs': '**KWARGS'}
35        True
36        """
37        super().__init__(name, bases, dct)
38        # inspect.getfullargspec() produces an object with attributes:
39        # args, varargs, kwonlyargs, annotations (among others)
40        # where args is a list that contains the names of positional parameters
41        # (including those with default values), varargs contains the name of
42        # the list of the variable-length positional parameters (i.e., the name
43        # of the * catch-all), and kwonlyargs contains a list of keyword-only
44        # parameters.
45        cls.init_signature = inspect.getfullargspec(cls.__init__)
46
47
48class Option(object, metaclass=abc.ABCMeta):
49    ''' Base class for command line options that may or may not take parameters
50    '''
51    # TODO: decide whether we need '-' or '/' as command line option prefix
52    # character under Windows. Currently: use "-'
53    if False and platform.system() == "Windows":
54        prefix = '/'
55    else:
56        prefix = '-'
57
58    def __init__(self, arg=None, prefix=None, is_input_file=False,
59                 is_output_file=False, checktype=None):
60        """ Define a mapping from a parameter name and value to command line
61        parameters.
62
63        arg is the command line parameter to which this should map, e.g.,
64            'v' or 'thr'. If not specified, the default name supplied to the
65            map() method will be used.
66        prefix is the command line parameter prefix to use; the default is the
67            class variable which currently is '-'. Some programs, e.g. bwc.pl,
68            want some parameters with a different prefix, e.g., ':complete'
69        is_input_file must be set to True if this parameter gives the filename
70            of an input file to the command. This will be used to generate FILE
71            lines in workunits.
72        is_output_file must be set to True if this parameter gives the filename
73            of an output file of the command. This will be used to generate
74            RESULT lines in workunits.
75        if checktype is specified, map() will assert that the value is an
76            instance of checktype.
77        """
78        self.arg = arg
79        if prefix:
80            self.prefix = prefix # hides the class variable
81        self.is_input_file = is_input_file
82        self.is_output_file = is_output_file
83        self.checktype = checktype
84        self.defaultname = None
85
86    def set_defaultname(self, defaultname):
87        """ Sets default name for the command line parameter. It must be specified
88        before calling map().
89
90        The reason for this is that command line parameters can be specified
91        in the constructor of Program sub-classes as, e.g.,
92          class Las(Program):
93            def __init__(lpb1 : Parameter()=None):
94        and the name "lpb1" of the Las constructor's parameter should also be
95        the default name of command line parameter. However, the Parameter()
96        gets instantiated when the ":" annotation gets parsed, i.e., at the
97        time the class definition of Las is parsed, and the fact that this
98        Parameter instance will act as an annotation to the "lpb1" parameter
99        is not known to the Parameter() instance. I.e., at its instantiation,
100        the Parameter instance cannot tell to which parameter it will belong.
101
102        This information must be filled in later, via set_defaultname(), which
103        is called from Program.__init__(). It uses introspection to find out
104        which Option objects belong to which Program constructor parameter, and
105        fills in the constructor parameters' name via set_defaultname("lpb1").
106
107        If the Option constructor had received an arg parameter, then that is
108        used instead of the defaultname, which allows for using different names
109        for the Program constructor's parameter and the command line parameter,
110        such as in
111          __init__(threads: Parameter("t")):
112        """
113        self.defaultname = defaultname
114
115    def get_arg(self):
116        assert not self.defaultname is None
117        return self.defaultname if self.arg is None else self.arg
118
119    def get_checktype(self):
120        return self.checktype
121
122    def map(self, value):
123        """ Public method that converts the Option instance into an array of
124        strings with command line parameters. It also checks the type, if
125        checktype was specified in the constructor.
126        """
127        assert not self.defaultname is None
128        if not self.checktype is None:
129            # If checktype is float, we allow both float and int as the type of
130            # value. Some programs accept parameters that are integers (such as
131            # admax) in scientific notation which is typed as floating point,
132            # thus we want to allow float parameters to be given in both
133            # integer and float type, so that passing, e.g., admax as an int
134            # does not trip the assertion
135            if self.checktype is float and type(value) is int:
136                # Write it to command line as an int
137                pass
138            elif self.checktype is int and type(value) is float:
139                # Can we convert this float to an int without loss?
140                if float(int(value)) == value:
141                    # Yes, convert it and write to command line as an int
142                    value = int(value)
143                else:
144                    raise ValueError("Cannot convert floating-point value %s "
145                                     "for parameter %s to an int without loss" %
146                                     (value, self.defaultname))
147            elif not isinstance(value, self.checktype):
148                raise TypeError("Value %s for parameter %s is of type %s, but "
149                                "checktype requires %s" %
150                                (repr(value), self.defaultname,
151                                 type(value).__name__,
152                                 self.checktype.__name__))
153        return self._map(value)
154
155    @abc.abstractmethod
156    def _map(self, value):
157        """ Private method that does the actual translation to an array of string
158        with command line parameters. Subclasses need to implement it.
159
160        A simple case of the Template Method pattern.
161        """
162        pass
163
164class PositionalParameter(Option):
165    ''' Positional command line parameter '''
166    def _map(self, value):
167        return [str(value)]
168
169class Parameter(Option):
170    ''' Command line option that takes a parameter '''
171    def _map(self, value):
172        return [self.prefix + self.get_arg(), str(value)]
173
174class ParameterEq(Option):
175    ''' Command line option that takes a parameter, used on command line in
176    the form of "key=value" '''
177    def _map(self, value):
178        return ["%s=%s" % (self.get_arg(), value)]
179
180class Toggle(Option):
181    ''' Command line option that does not take a parameter.
182    value is interpreted as a truth value, the option is either added or not
183    '''
184    def __init__(self, arg=None, prefix=None, is_input_file=False,
185                 is_output_file=False):
186        """ Overridden constructor that hard-codes checktype=bool """
187        super().__init__(arg=arg, prefix=prefix, is_input_file=is_input_file,
188                 is_output_file=is_output_file, checktype=bool)
189
190    def _map(self, value):
191        if value is True:
192            return [self.prefix + self.get_arg()]
193        elif value is False:
194            return []
195        else:
196            raise ValueError("Toggle.map() for %s requires a boolean "
197                             "type argument" % self.get_arg())
198
199class Sha1Cache(object):
200    """ A class that computes SHA1 sums for files and caches them, so that a
201    later request for the SHA1 sum for the same file is not computed again.
202    File identity is checked via the file's realpath and the file's inode, size,
203    and modification time.
204    """
205    def __init__(self):
206        self._sha1 = {}
207
208    @staticmethod
209    def _read_file_in_blocks(file_object):
210        blocksize = 65536
211        while True:
212            data = file_object.read(blocksize)
213            if not data:
214                break
215            yield data
216
217    def get_sha1(self, filename):
218        realpath = os.path.realpath(filename)
219        stat = os.stat(realpath)
220        file_id = (stat.st_ino, stat.st_size, stat.st_mtime)
221        # Check whether the file on disk changed
222        if realpath in self._sha1 and not self._sha1[realpath][1] == file_id:
223            logger = logging.getLogger("Sha1Cache")
224            logger.warning("File %s changed! Discarding old SHA1 sum", realpath)
225            del(self._sha1[realpath])
226        if not realpath in self._sha1:
227            logger = logging.getLogger("Sha1Cache")
228            logger.debug("Computing SHA1 for file %s", realpath)
229            sha1 = hashlib.sha1()
230            with open(realpath, "rb") as inputfile:
231                for data in self._read_file_in_blocks(inputfile):
232                    sha1.update(data)
233                self._sha1[realpath] = (sha1.hexdigest(), file_id)
234            logger.debug("SHA1 for file %s is %s", realpath,
235                         self._sha1[realpath])
236        return self._sha1[realpath][0]
237
238sha1cache = Sha1Cache()
239
240if os.name == "nt":
241    defaultsuffix = ".exe"
242else:
243    defaultsuffix = ""
244
245IS_MINGW = "MSYSTEM" in os.environ
246
247def translate_mingw_path(path):
248    if path is None:
249        return None
250    (drive, path) = os.path.splitdrive(path)
251    driveletter = drive.rstrip(":")
252    # If a drive letter is given, we need to know the absolute path as we
253    # don't handle per-drive current working directories
254    dirs = path.split(os.sep)
255    if driveletter:
256        assert path[0] == "\\"
257        dirs[1:1] = [driveletter]
258    return '/'.join(dirs)
259
260class Program(object, metaclass=InspectType):
261    ''' Base class that represents programs of the CADO suite
262
263    The Program base class is oblivious to how programs get input data,
264    or how they provide output data. It does, however, handle redirecting
265    stdin/stdout/stderr from/to files and accepts file names to/from which
266    to redirect. This is done so that workunits can be generated from
267    Program instances; in the workunit text, shell redirection syntax needs
268    to be used to connect stdio to files, so a Program instance needs to be
269    aware of which file names should be used for stdio redirection.
270
271    Sub-classes correspond to individual programs (such as las) and must
272    define these class variables:
273    binary: a string with the name of the binary executable file
274    name: a name used internally in the Python scripts for this program, must
275      start with a letter and contain only letters and digits; if the binary
276      file name is of this form, then that can be used
277    params: a mapping that tells how to translate configuration parameters to
278      command line options. The keys of the mapping are the keys as in the
279      configuration file, e.g., "verbose" in a configuartion file line
280      tasks.polyselect.verbose = 1
281      The values of the mapping are instances of subclasses of Option,
282      initialised with the command line parameter they should map to, e.g.,
283      "verbose" should map to an instance Toggle("v"), and "threads" should
284      map to an instance Parameter("t")
285
286    >>> p = Ls()
287    >>> p.make_command_line().replace("ls.exe", "/bin/ls")
288    '/bin/ls'
289    >>> p = Ls(stdout='foo')
290    >>> p.make_command_line().replace("ls.exe", "/bin/ls")
291    '/bin/ls > foo'
292    >>> p = Ls(stderr='foo')
293    >>> p.make_command_line().replace("ls.exe", "/bin/ls")
294    '/bin/ls 2> foo'
295    >>> p = Ls(stdout='foo', stderr='bar')
296    >>> p.make_command_line().replace("ls.exe", "/bin/ls")
297    '/bin/ls > foo 2> bar'
298    >>> p = Ls(stdout='foo', stderr='foo')
299    >>> p.make_command_line().replace("ls.exe", "/bin/ls")
300    '/bin/ls > foo 2>&1'
301    >>> p = Ls(stdout='foo', append_stdout=True)
302    >>> p.make_command_line().replace("ls.exe", "/bin/ls")
303    '/bin/ls >> foo'
304    >>> p = Ls(stderr='foo', append_stderr=True)
305    >>> p.make_command_line().replace("ls.exe", "/bin/ls")
306    '/bin/ls 2>> foo'
307    >>> p = Ls(stdout='foo', append_stdout=True, stderr='bar', append_stderr=True)
308    >>> p.make_command_line().replace("ls.exe", "/bin/ls")
309    '/bin/ls >> foo 2>> bar'
310    >>> p = Ls(stdout='foo', append_stdout=True, stderr='foo', append_stderr=True)
311    >>> p.make_command_line().replace("ls.exe", "/bin/ls")
312    '/bin/ls >> foo 2>&1'
313    >>> p = Ls('foo', 'bar')
314    >>> p.make_command_line().replace("ls.exe", "/bin/ls")
315    '/bin/ls foo bar'
316    >>> p = Ls('foo', 'bar', long = True)
317    >>> p.make_command_line().replace("ls.exe", "/bin/ls")
318    '/bin/ls -l foo bar'
319    '''
320
321    path = '.'
322    subdir = ""
323    paramnames = ("execpath", "execsubdir", "execbin", "execsuffix",
324        "runprefix")
325    # These should be abstract properties, but we want to reference them as
326    # class attributes, which properties can't. Ergo dummy variables
327    binary = None
328
329    # This class variable definition should not be here. It gets overwritten
330    # when the InspectType meta-class creates the class object. The only purpose
331    # is to make pylint shut up about the class not having an init_signature
332    # attribute
333    init_signature = None
334
335    def __init__(self, options, stdin=None,
336                 stdout=None, append_stdout=False, stderr=None,
337                 append_stderr=False, background=False, execpath=None,
338                 execsubdir=None, execbin=None, execsuffix=defaultsuffix,
339                 runprefix=None, skip_check_binary_exists=None):
340        ''' Takes a dict of of command line options. Defaults are filled in
341        from the cadoparams.Parameters instance parameters.
342
343        The stdin, stdout, and stderr parameters accept strings. If a string is
344        given, it is interpreted as the file name to use for redirecting that
345        stdio stream. In workunit generation, the file name is used for shell
346        redirection.
347        '''
348
349        # The function annotations define how to convert each value in
350        # self.parameters to command line arguments, but the annotations are
351        # stored in an unordered dict. We would like to put the command line
352        # parameters in a particular order, so that, for example, positional
353        # parameters are placed correctly. We also need to handle a variable-
354        # length positional parameter list.
355        # That is, we need three lists:
356        # - Individual positional parameters
357        # - Variable-length positional parameter (e.g., list of filenames)
358        # - Keyword parameters
359
360        # First off, we set the default names for Option subclass instances.
361        # Since the Option instances are always the same (the annotation values
362        # are evaluated once at class creation time), we'll set the defaultnames
363        # on the same Option instances again each time a Program subclass gets
364        # instantiated, but that does not hurt.
365        for (name, option) in self.__init__.__annotations__.items():
366            option.set_defaultname(name)
367
368        # "options" contains all local symbols defined in the sub-class'
369        # __init__() method. This includes self, __class__, and a few others.
370        # Filter them so that only those that correspond to annotated
371        # parameters remain.
372        filtered = self._filter_annotated_keys(options)
373
374        # The default value for all __init__() parameters that correspond to
375        # command line parameters is always None. Remove those entries.
376        # We could in principle look up the default values from the .defaults
377        # attribute given by inspect.getfullargspec(), but it's easier to
378        # use the convention that the default is always None.
379        filtered = {key:options[key] for key in filtered \
380                    if not options[key] is None}
381
382        self.parameters = filtered
383
384        self.stdin = stdin
385        self.stdout = stdout
386        self.append_stdout = append_stdout
387        self.stderr = stderr
388        self.append_stderr = append_stderr
389        self.runprefix = runprefix
390
391        # If we are to run in background, we add " &" to the command line.
392        # We require that stdout and stderr are redirected to files.
393        self.background = background
394        if self.background and (stdout is None or stderr is None):
395            raise Exception("Programs to run in background must redirect "
396                            "stdout and stderr")
397
398        # Look for location of the binary executable at __init__, to avoid
399        # calling os.path.isfile() multiple times
400        path = str(execpath or self.path)
401        subdir = str(execsubdir or self.subdir)
402        binary = str(execbin or self.binary) + execsuffix
403        execfile = os.path.normpath(os.sep.join([path, binary]))
404        execsubfile = os.path.normpath(os.sep.join([path, subdir, binary]))
405        self.suggest_subdir=dict()
406        if execsubfile != execfile and os.path.isfile(execsubfile):
407            self.execfile = execsubfile
408            self.suggest_subdir[self.execfile]=subdir
409        elif os.path.isfile(execfile):
410            self.execfile = execfile
411        else:
412            self.execfile = binary
413            if not skip_check_binary_exists:
414                # Raising an exception here makes it impossible to run
415                # doctests for each Program subclass
416                raise Exception("Binary executable file %s not found (did you run \"make\" ?)" % execfile)
417
418    @classmethod
419    def _get_option_annotations(cls):
420        """ Extract the elements of this class' __init__() annotations where
421        the annotation is an Option instance
422        """
423        return {key:val for (key, val) in cls.__init__.__annotations__.items()
424                if isinstance(val, Option)}
425
426    @classmethod
427    def _filter_annotated_options(cls, keys):
428        """ From the list of keys given in "keys", return those that are
429        parameters of the __init__() method of this class and annotated with an
430        Option instance. Returns a dictionary of key:Option-instance pairs.
431        """
432        options = cls._get_option_annotations()
433        return {key:options[key] for key in keys if key in options}
434
435    @classmethod
436    def _filter_annotated_keys(cls, keys):
437        """ From the list of keys given in "keys", return those that are
438        parameters of the __init__() method of this class and annotated with an
439        Option instance. Returns a list of keys, where order w.r.t. the input
440        keys is preserved.
441        """
442        options = cls._get_option_annotations()
443        return [key for key in keys if key in options]
444
445    @classmethod
446    def get_accepted_keys(cls):
447        """ Return all parameter file keys which can be used by this program,
448        including those that don't directly map to command line parameters,
449        like those specifying search paths for the binary executable file.
450
451        This list is the union of annotated positional and keyword parameters to
452        the constructor, plus the Program class defined parameters. Note that an
453        *args parameter (stored in cls.init_signature.varargs) should not be
454        included here; there is no way to specify a variable-length set of
455        positional parameters in the parameter file.
456        """
457        # The fact that we want to exclude varargs is why we need the inspect
458        # info here.
459
460        # Get a map of keys to Option instances
461        parameters = cls._filter_annotated_options(
462            cls.init_signature.args + cls.init_signature.kwonlyargs)
463
464        # Turn it into a map of keys to checktype
465        parameters = {key:parameters[key].get_checktype() for key in parameters}
466
467        # Turn checktypes that are not None into one-element lists.
468        # The one-element list is treated by cadoparams.Parameters.myparams()
469        # as a non-mandatory typed parameter.
470        parameters = {key:None if checktype is None else [checktype]
471            for key,checktype in parameters.items()}
472
473        return cadoparams.UseParameters.join_params(parameters,
474                                                    Program.paramnames)
475
476    def get_stdout(self):
477        return self.stdout
478
479    def get_stderr(self):
480        return self.stderr
481
482    def get_stdio(self):
483        """ Returns a 3-tuple with information on the stdin, stdout, and stderr
484        streams for this program.
485
486        The first entry is for stdin, and is either a file name or None.
487        The second entry is for stdout and is a 2-tuple, whose first entry is
488            either a file name or None, and whose second entry is True if we
489            should append to the file and False if we should not.
490        The third entry is like the second, but for stderr.
491        """
492        return (self.stdin,
493                (self.stdout, self.append_stdout),
494                (self.stderr, self.append_stderr)
495            )
496
497    def _get_files(self, is_output):
498        """ Helper method to get a set of input/output files for this Program.
499        This method returns the list of filenames without considering files for
500        stdio redirection. If "is_output" evaluates to True, the list of output
501        files is generated, otherwise the list of input files.
502        """
503        files = set()
504        parameters = self.init_signature.args + self.init_signature.kwonlyargs
505        if not self.init_signature.varargs is None:
506            parameters.append(self.init_signature.varargs)
507        parameters = self._filter_annotated_keys(parameters)
508        for param in parameters:
509            ann = self.__init__.__annotations__[param]
510            if param in self.parameters and \
511                    (ann.is_output_file if is_output else ann.is_input_file):
512                if param == self.init_signature.varargs:
513                    # vararg is a list; we need to convert each entry to string
514                    # and append this list of strings to files
515                    files |= set(map(str, self.parameters[param]))
516                else:
517                    files |= {str(self.parameters[param])}
518        return files
519
520    def get_input_files(self, with_stdio=True):
521        """ Returns a list of input files to this Program instance. If
522        with_stdio is True, includes stdin if such redirection is used.
523        """
524        input_files = self._get_files(is_output=False)
525        if with_stdio and isinstance(self.stdin, str):
526            input_files |= {self.stdin}
527        return list(input_files)
528
529    def get_output_files(self, with_stdio=True):
530        """ Returns a list of output files. If with_stdio is True, includes
531        files for stdout/stderr redirection if such redirection is used.
532        """
533        output_files = self._get_files(is_output=True)
534        if with_stdio:
535            for filename in (self.stdout, self.stderr):
536                if isinstance(filename, str):
537                    output_files |= {filename}
538        return list(output_files)
539
540    def get_exec_file(self):
541        return self.execfile
542
543    def get_exec_files(self):
544        return [self.get_exec_file()]
545
546    @staticmethod
547    def translate_path(filename, filenametrans=None):
548        if not filenametrans is None and str(filename) in filenametrans:
549            return filenametrans[str(filename)]
550        return str(filename)
551
552    def make_command_array(self, filenametrans=None):
553        # Begin command line with program to execute
554        command = []
555        if not self.runprefix is None:
556            command=self.runprefix.split(' ')
557        command.append(self.translate_path(self.get_exec_file(), filenametrans))
558
559        # Add keyword command line parameters, then positional parameters
560        parameters = self.init_signature.kwonlyargs + self.init_signature.args
561        parameters = self._filter_annotated_keys(parameters)
562        for key in parameters:
563            if key in self.parameters:
564                ann = self.__init__.__annotations__[key]
565                value = self.parameters[key]
566                # If this is an input or an output file name, we may have to
567                # translate it, e.g., for workunits
568                assert not (ann.is_input_file and ann.is_output_file)
569                if ann.is_input_file:
570                    value = self.translate_path(value, filenametrans)
571                elif ann.is_output_file:
572                    value = self.translate_path(value, filenametrans)
573                command += ann.map(value)
574
575        # Add positional command line parameters
576        key = self.init_signature.varargs
577        if not key is None:
578            paramlist = self.parameters[key]
579            ann = self.__init__.__annotations__.get(key, None)
580            for value in paramlist:
581                command += ann.map(value)
582        return command
583
584    def make_command_line(self, in_wu=False, quote=True, filenametrans=None):
585        """ Make a shell command line for this program.
586
587        If files are given for stdio redirection, the corresponding redirection
588        tokens are added to the command line.
589        """
590        assert not (in_wu and quote)
591
592        cmdarr = self.make_command_array(filenametrans=filenametrans)
593        if quote:
594            cmdline = " ".join(map(cadocommand.shellquote, cmdarr))
595        else:
596            cmdline = " ".join(cmdarr)
597
598        if not in_wu and isinstance(self.stdin, str):
599            assert quote
600            translated = self.translate_path(self.stdin,
601                                             filenametrans=filenametrans)
602            cmdline += ' < ' + cadocommand.shellquote(translated)
603        if not in_wu and isinstance(self.stdout, str):
604            assert quote
605            redir = ' >> ' if self.append_stdout else ' > '
606            translated = self.translate_path(self.stdout,
607                                             filenametrans=filenametrans)
608            cmdline += redir + cadocommand.shellquote(translated)
609        if not in_wu and not self.stderr is None and self.stderr is self.stdout:
610            assert quote
611            cmdline += ' 2>&1'
612        elif not in_wu and isinstance(self.stderr, str):
613            assert quote
614            redir = ' 2>> ' if self.append_stderr else ' 2> '
615            translated = self.translate_path(self.stderr,
616                                             filenametrans=filenametrans)
617            cmdline += redir + cadocommand.shellquote(translated)
618        if self.background:
619            assert quote
620            cmdline += " &"
621        return cmdline
622
623    def make_wu(self, wuname):
624        filenametrans = {}
625        counters = {    "FILE": 1,
626                        "EXECFILE": 1,
627                        "RESULT": 1,
628                        "STDOUT": 1,
629                        "STDERR": 1,
630                        "STDIN": 1,
631                   }
632        def append_file(wu, key, filename, with_checksum=True):
633            if not key.startswith("STD"):
634                assert filename not in filenametrans
635                filenametrans[filename] = "${%s%d}" % (key, counters[key])
636            counters[key] += 1
637            wu.append('%s %s' % (key, os.path.basename(filename)))
638            if with_checksum:
639                wu.append('CHECKSUM %s' % sha1cache.get_sha1(filename))
640            if self.suggest_subdir.get(filename, None):
641                wu.append('SUGGEST_%s %s' % (key, self.suggest_subdir[filename]))
642
643        workunit = ['WORKUNIT %s' % wuname]
644        for filename in self.get_input_files():
645            append_file(workunit, 'FILE', str(filename))
646        for filename in self.get_exec_files():
647            append_file(workunit, 'EXECFILE', str(filename))
648        for filename in self.get_output_files():
649            append_file(workunit, 'RESULT', str(filename), with_checksum=False)
650        if self.stdout is not None:
651            append_file(workunit, 'STDOUT', str(self.stdout), with_checksum=False)
652        if self.stderr is not None:
653            append_file(workunit, 'STDERR', str(self.stdout), with_checksum=False)
654        if self.stdin is not None:
655            append_file(workunit, 'STDIN', str(self.stdin))
656
657        cmdline = self.make_command_line(filenametrans=filenametrans, in_wu=True, quote=False)
658        workunit.append('COMMAND %s' % cmdline)
659        workunit.append("") # Make a trailing newline
660        return '\n'.join(workunit)
661
662
663class Polyselect(Program):
664    """
665    >>> p = Polyselect(P=5, N=42, degree=4, verbose=True, skip_check_binary_exists=True)
666    >>> p.make_command_line().replace(defaultsuffix + " ", " ", 1)
667    'polyselect -P 5 -N 42 -degree 4 -v'
668    >>> p = Polyselect(P=5, N=42, degree=4, verbose=True, skip_check_binary_exists=True)
669    >>> p.make_command_line().replace(defaultsuffix + " ", " ", 1)
670    'polyselect -P 5 -N 42 -degree 4 -v'
671    """
672    binary = "polyselect"
673    name = binary
674    subdir = "polyselect"
675
676    def __init__(self, *,
677                 P : Parameter(checktype=int)=None,
678                 N : Parameter(checktype=int)=None,
679                 degree : Parameter(checktype=int)=None,
680                 verbose : Toggle("v")=None,
681                 quiet : Toggle("q")=None,
682                 threads : Parameter("t", checktype=int)=None,
683                 admin : Parameter(checktype=int)=None,
684                 admax : Parameter(checktype=int)=None,
685                 incr : Parameter(checktype=int)=None,
686                 nq : Parameter(checktype=int)=None,
687                 maxtime : Parameter(checktype=float)=None,
688                 # -out is filename for msieve-format output ; absolutely
689                 # no reason to have it here.
690                 # out : Parameter(is_output_file=True)=None,
691                 printdelay : Parameter("s", checktype=int)=None,
692                 keep: Parameter(checktype=int)=None,
693                 sopteffort: Parameter(checktype=int)=None,
694                 **kwargs):
695        super().__init__(locals(), **kwargs)
696
697
698class PolyselectRopt(Program):
699    """
700    >>> p = PolyselectRopt(ropteffort=5, inputpolys="foo.polys", verbose=True, skip_check_binary_exists=True)
701    >>> p.make_command_line().replace(defaultsuffix + " ", " ", 1)
702    'polyselect_ropt -v -inputpolys foo.polys -ropteffort 5'
703    """
704    binary = "polyselect_ropt"
705    name = binary
706    subdir = "polyselect"
707
708    def __init__(self, *,
709                 verbose : Toggle("v")=None,
710                 threads : Parameter("t", checktype=int)=None,
711                 inputpolys : Parameter(is_input_file=True)=None,
712                 ropteffort: Parameter(checktype=float)=None,
713                 area : Parameter(checktype=float)=None,
714                 Bf : Parameter(checktype=float)=None,
715                 Bg : Parameter(checktype=float)=None,
716                 **kwargs):
717        super().__init__(locals(), **kwargs)
718
719class Polyselect3(Program):
720    binary = "polyselect3"
721    name = binary
722    subdir = "polyselect"
723
724    def __init__(self, *,
725                 verbose : Toggle("v")=None,
726                 threads : Parameter("t", checktype=int)=None,
727                 num :  Parameter(checktype=int)=None,
728                 poly : Parameter(is_input_file=True),
729                 Bf : Parameter(checktype=float)=None,
730                 Bg : Parameter(checktype=float)=None,
731                 area : Parameter(checktype=float)=None,
732                 **kwargs):
733        super().__init__(locals(), **kwargs)
734
735class PolyselectGFpn(Program):
736    binary = "polyselect_gfpn"
737    name = binary
738    subdir = "polyselect"
739
740    def __init__(self, *,
741                 verbose : Toggle("v")=None,
742                 p: Parameter(checktype=int)=None,
743                 n: Parameter(checktype=int)=None,
744                 out: Parameter(is_output_file=True)=None,
745                 **kwargs):
746        super().__init__(locals(), **kwargs)
747
748class PolyselectJL(Program):
749    binary = "dlpolyselect"
750    name = binary
751    subdir = "polyselect"
752
753    def __init__(self, *,
754                 verbose : Toggle("v")=None,
755                 N: Parameter(checktype=int)=None,
756                 easySM: Parameter(checktype=int)=None,
757                 df: Parameter(checktype=int)=None,
758                 dg: Parameter(checktype=int)=None,
759                 area : Parameter(checktype=float)=None,
760                 Bf : Parameter(checktype=float)=None,
761                 Bg : Parameter(checktype=float)=None,
762                 bound: Parameter(checktype=int)=None,
763                 modm: Parameter(checktype=int)=None,
764                 modr: Parameter(checktype=int)=None,
765                 skew : Toggle()=None,
766                 threads : Parameter("t", checktype=int)=None,
767                 **kwargs):
768        super().__init__(locals(), **kwargs)
769
770class MakeFB(Program):
771    """
772    >>> p = MakeFB(poly="foo.poly", lim=1, skip_check_binary_exists=True)
773    >>> p.make_command_line().replace(defaultsuffix + " ", " ", 1)
774    'makefb -poly foo.poly -lim 1'
775    >>> p = MakeFB(poly="foo.poly", lim=1, maxbits=5, stdout="foo.roots", skip_check_binary_exists=True)
776    >>> p.make_command_line().replace(defaultsuffix + " ", " ", 1)
777    'makefb -poly foo.poly -lim 1 -maxbits 5 > foo.roots'
778    """
779    binary = "makefb"
780    name = binary
781    subdir = "sieve"
782
783    def __init__(self, *,
784                 poly: Parameter(is_input_file=True),
785                 lim: Parameter(checktype=int),
786                 maxbits: Parameter(checktype=int)=None,
787                 out: Parameter(is_output_file=True)=None,
788                 side: Parameter(checktype=int)=None,
789                 threads : Parameter("t", checktype=int)=None,
790                 **kwargs):
791        super().__init__(locals(), **kwargs)
792
793
794class FreeRel(Program):
795    """
796    >>> p = FreeRel(poly="foo.poly", renumber="foo.renumber", lpb0=1, lpb1=2, out="foo.freerel", skip_check_binary_exists=True)
797    >>> p.make_command_line().replace(defaultsuffix + " ", " ", 1)
798    'freerel -poly foo.poly -renumber foo.renumber -lpb0 1 -lpb1 2 -out foo.freerel'
799    >>> p = FreeRel(poly="foo.poly", renumber="foo.renumber", lpb0=1, lpb1=2, out="foo.freerel", badideals="foo.bad", pmin=123, pmax=234, skip_check_binary_exists=True)
800    >>> p.make_command_line().replace(defaultsuffix + " ", " ", 1)
801    'freerel -poly foo.poly -renumber foo.renumber -lpb0 1 -lpb1 2 -out foo.freerel -badideals foo.bad -pmin 123 -pmax 234'
802    """
803    binary = "freerel"
804    name = binary
805    subdir = "sieve"
806    def __init__(self, *,
807                 poly: Parameter(is_input_file=True),
808                 renumber: Parameter(is_output_file=True),
809                 lpb0: Parameter("lpb0", checktype=int),
810                 lpb1: Parameter("lpb1", checktype=int),
811                 out: Parameter(is_output_file=True),
812                 badideals: Parameter(is_output_file=True)=None,
813                 pmin: Parameter(checktype=int)=None,
814                 pmax: Parameter(checktype=int)=None,
815                 lcideals: Toggle() = None,
816                 threads: Parameter("t", checktype=int)=None,
817                 **kwargs):
818        super().__init__(locals(), **kwargs)
819
820class Las(Program):
821    binary = "las"
822    name = binary
823    subdir = "sieve"
824    def __init__(self,
825                 poly: Parameter(is_input_file=True),
826                 q0: Parameter(checktype=int),
827                 I: Parameter(checktype=int)=None,
828                 A: Parameter(checktype=int)=None,
829                 q1: Parameter(checktype=int)=None,
830                 rho: Parameter(checktype=int)=None,
831                 skipped: Parameter(checktype=int)=None,
832                 tdthresh: Parameter(checktype=int)=None,
833                 bkthresh: Parameter(checktype=int)=None,
834                 bkthresh1: Parameter(checktype=int)=None,
835                 bkmult: Parameter()=None,
836                 lim0: Parameter(checktype=int)=None,
837                 lim1: Parameter(checktype=int)=None,
838                 lpb0: Parameter(checktype=int)=None,
839                 lpb1: Parameter(checktype=int)=None,
840                 mfb0: Parameter(checktype=int)=None,
841                 mfb1: Parameter(checktype=int)=None,
842                 batchlpb0: Parameter(checktype=int)=None,
843                 batchlpb1: Parameter(checktype=int)=None,
844                 batchmfb0: Parameter(checktype=int)=None,
845                 batchmfb1: Parameter(checktype=int)=None,
846                 lambda0: Parameter(checktype=float)=None,
847                 lambda1: Parameter(checktype=float)=None,
848                 ncurves0: Parameter(checktype=int)=None,
849                 ncurves1: Parameter(checktype=int)=None,
850                 skewness: Parameter("S", checktype=float)=None,
851                 verbose: Toggle("v")=None,
852                 powlim0: Parameter(checktype=int)=None,
853                 powlim1: Parameter(checktype=int)=None,
854                 factorbase0: Parameter("fb0", is_input_file=True)=None,
855                 factorbase1: Parameter("fb1", is_input_file=True)=None,
856                 out: Parameter(is_output_file=True)=None,
857                 threads: Parameter("t")=None,
858                 batch: Toggle()=None,
859                 batch0: Parameter("batch0", is_input_file=True)=None,
860                 batch1: Parameter("batch1", is_input_file=True)=None,
861                 sqside: Parameter(checktype=int)=None,
862                 dup: Toggle()=None,
863                 galois: Parameter() = None,
864                 sublat: Parameter(checktype=int)=None,
865                 allow_largesq: Toggle("allow-largesq")=None,
866                 allow_compsq: Toggle("allow-compsq")=None,
867                 qfac_min: Parameter("qfac-min", checktype=int)=None,
868                 qfac_max: Parameter("qfac-max", checktype=int)=None,
869                 adjust_strategy: Parameter("adjust-strategy", checktype=int)=None,
870                 stats_stderr: Toggle("stats-stderr")=None,
871                 # We have no checktype for parameters of the form <int>,<int>,
872                 # so these are passed just as strings
873                 traceab: Parameter() = None,
874                 traceij: Parameter() = None,
875                 traceNx: Parameter() = None,
876                 # Let's make fbcache neither input nor output file. It should
877                 # not be distributed to clients, nor sent back to the server.
878                 # It's a local temp file, but re-used between different runs.
879                 fbcache: Parameter("fbc")=None,
880                 **kwargs):
881        super().__init__(locals(), **kwargs)
882        if "I" not in self.parameters and "A" not in self.parameters:
883            raise KeyError("Lattice siever requires either I or A be set -- consider setting tasks.I or tasks.A")
884
885
886
887
888class Duplicates1(Program):
889    binary = "dup1"
890    name = binary
891    subdir = "filter"
892    def __init__(self,
893                 *args: PositionalParameter(is_input_file=True),
894                 prefix : Parameter(),
895                 out: Parameter()=None,
896                 outfmt: Parameter()=None,
897                 bzip: Toggle("bz")=None,
898                 only_ab: Toggle("ab")=None,
899                 abhexa: Toggle()=None,
900                 force_posix_threads: Toggle("force-posix-threads")=None,
901                 only: Parameter(checktype=int)=None,
902                 nslices_log: Parameter("n", checktype=int)=None,
903                 lognrels: Parameter(checktype=int)=None,
904                 filelist: Parameter(is_input_file=True)=None,
905                 basepath: Parameter()=None,
906                 **kwargs):
907        super().__init__(locals(), **kwargs)
908
909
910class Duplicates2(Program):
911    binary = "dup2"
912    name = binary
913    subdir = "filter"
914    def __init__(self,
915                 *args: PositionalParameter(is_input_file=True),
916                 poly: Parameter(is_input_file=True),
917                 rel_count: Parameter("nrels", checktype=int),
918                 renumber: Parameter(is_input_file=True),
919                 filelist: Parameter(is_input_file=True)=None,
920                 force_posix_threads: Toggle("force-posix-threads")=None,
921                 dlp: Toggle("dl")=None,
922                 **kwargs):
923        super().__init__(locals(), **kwargs)
924
925class GaloisFilter(Program):
926    binary = "filter_galois"
927    name = binary
928    subdir = "filter"
929    def __init__(self,
930                 *args: PositionalParameter(is_input_file=True),
931                 nrels: Parameter(checktype=int),
932                 poly: Parameter(is_input_file=True),
933                 renumber: Parameter(is_input_file=True),
934                 filelist: Parameter(is_input_file=True)=None,
935                 basepath: Parameter()=None,
936                 galois: Parameter("galois")=None,
937                 **kwargs):
938        super().__init__(locals(), **kwargs)
939
940
941class Purge(Program):
942    binary = "purge"
943    name = binary
944    subdir = "filter"
945    def __init__(self,
946                 *args: PositionalParameter(is_input_file=True),
947                 out: Parameter(is_output_file=True),
948                 filelist: Parameter(is_input_file=True)=None,
949                 basepath: Parameter()=None,
950                 subdirlist: Parameter()=None,
951                 nrels: Parameter(checktype=int)=None,
952                 outdel: Parameter(is_output_file=True)=None,
953                 keep: Parameter(checktype=int)=None,
954                 col_minindex: Parameter("col-min-index", checktype=int)=None,
955                 nprimes: Parameter("col-max-index", checktype=int)=None,
956                 threads: Parameter("t", checktype=int)=None,
957                 npass: Parameter(checktype=int)=None,
958                 force_posix_threads: Toggle("force-posix-threads")=None,
959                 required_excess: Parameter(checktype=float)=None,
960                 **kwargs):
961        super().__init__(locals(), **kwargs)
962
963class Merge(Program):
964    binary = "merge"
965    name = binary
966    subdir = "filter"
967    def __init__(self,
968                 purged: Parameter("mat", is_input_file=True),
969                 out: Parameter(is_output_file=True),
970                 skip: Parameter(checktype=int)=None,
971                 target_density: Parameter(checktype=float)=None,
972                 threads: Parameter("t", checktype=int)=None,
973                 force_posix_threads: Toggle("force-posix-threads")=None,
974                 **kwargs):
975        super().__init__(locals(), **kwargs)
976
977class MergeDLP(Program):
978    binary = "merge-dl"
979    name = binary
980    subdir = "filter"
981    def __init__(self,
982                 purged: Parameter("mat", is_input_file=True),
983                 out: Parameter(is_output_file=True),
984                 skip: Parameter(checktype=int)=None,
985                 target_density: Parameter(checktype=float)=None,
986                 threads: Parameter("t", checktype=int)=None,
987                 **kwargs):
988        super().__init__(locals(), **kwargs)
989
990# Todo: define is_input_file/is_output_file for remaining programs
991class Replay(Program):
992    binary = "replay"
993    name = binary
994    subdir = "filter"
995    def __init__(self,
996                 purged: Parameter()=None,
997                 history: Parameter("his")=None,
998                 index: Parameter()=None,
999                 out: Parameter()=None,
1000                 for_msieve: Toggle()=None,
1001                 skip: Parameter(checktype=int)=None,
1002                 force_posix_threads: Toggle("force-posix-threads")=None,
1003                 bwcostmin: Parameter(checktype=int)=None,
1004                 **kwargs):
1005        super().__init__(locals(), **kwargs)
1006
1007class ReplayDLP(Program):
1008    binary = "replay-dl"
1009    name = binary
1010    subdir = "filter"
1011    def __init__(self,
1012                 purged: Parameter()=None,
1013                 ideals: Parameter()=None,
1014                 history: Parameter("his")=None,
1015                 index: Parameter()=None,
1016                 out: Parameter()=None,
1017                 skip: Parameter()=None,
1018                 **kwargs):
1019        super().__init__(locals(), **kwargs)
1020
1021class NumberTheory(Program):
1022    binary = "badideals"
1023    name = binary
1024    subdir = "utils"
1025    def __init__(self,
1026                 poly: Parameter(),
1027                 ell: Parameter(),
1028                 badidealinfo: Parameter(),
1029                 badideals: Parameter(),
1030                 **kwargs):
1031        super().__init__(locals(), **kwargs)
1032
1033class BWC(Program):
1034    binary = "bwc.pl"
1035    name = "bwc"
1036    subdir = "linalg/bwc"
1037    def __init__(self,
1038                 complete: Toggle(prefix=":")=None,
1039                 dryrun: Toggle("d")=None,
1040                 verbose: Toggle("v")=None,
1041                 mpi: ParameterEq()=None,
1042                 lingen_mpi: ParameterEq()=None,
1043                 allow_zero_on_rhs: ParameterEq()=None,
1044                 threads: ParameterEq("thr")=None,
1045                 m: ParameterEq()=None,
1046                 n: ParameterEq()=None,
1047                 nullspace: ParameterEq()=None,
1048                 interval: ParameterEq()=None,
1049                 ys: ParameterEq()=None,
1050                 matrix: ParameterEq()=None,
1051                 rhs: ParameterEq()=None,
1052                 prime: ParameterEq()=None,
1053                 wdir: ParameterEq()=None,
1054                 mpiexec: ParameterEq()=None,
1055                 hosts: ParameterEq()=None,
1056                 hostfile: ParameterEq()=None,
1057                 interleaving: ParameterEq()=None,
1058                 bwc_bindir: ParameterEq()=None,
1059                 mm_impl: ParameterEq()=None,
1060                 cpubinding: ParameterEq()=None,
1061                 # lingen_threshold: ParameterEq()=None,
1062                 precmd: ParameterEq()=None,
1063                 # put None below for a random seed,
1064                 # or any value (for example 1) for a fixed seed
1065                 seed: ParameterEq()=None,
1066                 **kwargs):
1067        if os.name == "nt":
1068            kwargs.setdefault("runprefix", "perl.exe")
1069            kwargs.setdefault("execsuffix", "")
1070        if IS_MINGW:
1071            matrix = translate_mingw_path(matrix)
1072            wdir = translate_mingw_path(wdir)
1073            mpiexec = translate_mingw_path(mpiexec)
1074            hostfile = translate_mingw_path(hostfile)
1075            if bwc_bindir is None and "execpath" in kwargs:
1076                bwc_bindir = os.path.normpath(os.sep.join([kwargs["execpath"], self.subdir]))
1077            bwc_bindir = translate_mingw_path(bwc_bindir)
1078        super().__init__(locals(), **kwargs)
1079    def make_command_line(self, *args, **kwargs):
1080	    c = super().make_command_line(*args, **kwargs)
1081	    return c
1082
1083class SM(Program):
1084    binary = "sm"
1085    name = binary
1086    subdir = "filter"
1087    def __init__(self, *,
1088                 poly: Parameter(),
1089                 purged: Parameter(),
1090                 index: Parameter(),
1091                 out: Parameter(),
1092                 ell: Parameter(),
1093                 nsm: Parameter()=None,
1094                 sm_mode: Parameter("sm-mode")=None,
1095                 **kwargs):
1096        super().__init__(locals(), **kwargs)
1097
1098class ReconstructLog(Program):
1099    binary = "reconstructlog-dl"
1100    name = binary
1101    subdir = "filter"
1102    def __init__(self, *,
1103                 ell: Parameter(),
1104                 threads: Parameter("mt")=None,
1105                 ker: Parameter("log", is_input_file=True),
1106                 dlog: Parameter("out"),
1107                 renumber: Parameter(),
1108                 poly: Parameter(),
1109                 purged: Parameter(),
1110                 ideals: Parameter(),
1111                 relsdel: Parameter(),
1112                 nrels: Parameter(),
1113                 partial: Toggle()=None,
1114                 sm_mode: Parameter("sm-mode")=None,
1115                 nsm: Parameter(),
1116                 **kwargs):
1117        super().__init__(locals(), **kwargs)
1118
1119class Descent(Program):
1120    binary = "descent.py"
1121    name = "descent"
1122    subdir = "scripts"
1123    def __init__(self, *,
1124                 target: Parameter(prefix="--"),
1125                 gfpext: Parameter(prefix="--"),
1126                 prefix: Parameter(prefix="--"),
1127                 datadir: Parameter(prefix="--"),
1128                 cadobindir: Parameter(prefix="--"),
1129                 descent_hint: Parameter("descent-hint", prefix="--",
1130                     is_input_file=True),
1131                 init_I: Parameter("init-I", prefix="--"),
1132                 init_ncurves: Parameter("init-ncurves", prefix="--"),
1133                 init_lpb: Parameter("init-lpb", prefix="--"),
1134                 init_lim: Parameter("init-lim", prefix="--"),
1135                 init_mfb: Parameter("init-mfb", prefix="--"),
1136                 init_tkewness: Parameter("init-tkewness", prefix="--"),
1137                 init_minB1: Parameter("init-minB1", prefix="--")=None,
1138                 init_mineff: Parameter("init-mineff", prefix="--")=None,
1139                 init_maxeff: Parameter("init-maxeff", prefix="--")=None,
1140                 init_side: Parameter("init-side", prefix="--")=None,
1141                 sm_mode: Parameter("sm-mode", prefix="--")=None,
1142                 I: Parameter(prefix="--"),
1143                 lpb0: Parameter(prefix="--"),
1144                 lpb1: Parameter(prefix="--"),
1145                 mfb0: Parameter(prefix="--"),
1146                 mfb1: Parameter(prefix="--"),
1147                 lim0: Parameter(prefix="--"),
1148                 lim1: Parameter(prefix="--"),
1149                 ell: Parameter(prefix="--"),
1150                 **kwargs):
1151        super().__init__(locals(), **kwargs)
1152
1153
1154class Characters(Program):
1155    binary = "characters"
1156    name = binary
1157    subdir = "linalg"
1158    def __init__(self, *,
1159                 poly: Parameter(),
1160                 purged: Parameter(),
1161                 index: Parameter(),
1162                 heavyblock: Parameter(),
1163                 out: Parameter(),
1164                 wfile: Parameter("ker"),
1165                 lpb0: Parameter("lpb0"),
1166                 lpb1: Parameter("lpb1"),
1167                 nchar: Parameter()=None,
1168                 nratchars: Parameter()=None,
1169                 threads: Parameter("t")=None,
1170                 **kwargs):
1171        super().__init__(locals(), **kwargs)
1172
1173class Sqrt(Program):
1174    binary = "sqrt"
1175    name = binary
1176    subdir = "sqrt"
1177    def __init__(self, *,
1178                 poly: Parameter(),
1179                 prefix: Parameter(),
1180                 purged: Parameter()=None,
1181                 index: Parameter()=None,
1182                 kernel: Parameter("ker")=None,
1183                 dep: Parameter()=None,
1184                 threads : Parameter("t", checktype=int)=None,
1185                 ab: Toggle()=None,
1186                 side0: Toggle()=None,
1187                 side1: Toggle()=None,
1188                 gcd: Toggle()=None,
1189                 **kwargs):
1190        super().__init__(locals(), **kwargs)
1191
1192class CadoNFSClient(Program):
1193    binary = "cado-nfs-client.py"
1194    name = "cado_nfs_client"
1195    subdir = ""
1196    def __init__(self,
1197                 server: Parameter(prefix='--'),
1198                 daemon: Toggle(prefix='--')=None,
1199                 keepoldresult: Toggle(prefix='--')=None,
1200                 nosha1check: Toggle(prefix='--')=None,
1201                 dldir: Parameter(prefix='--')=None,
1202                 workdir: Parameter(prefix='--')=None,
1203                 bindir: Parameter(prefix='--')=None,
1204                 clientid: Parameter(prefix='--')=None,
1205                 basepath: Parameter(prefix='--')=None,
1206                 getwupath: Parameter(prefix='--')=None,
1207                 loglevel: Parameter(prefix='--')=None,
1208                 postresultpath: Parameter(prefix='--')=None,
1209                 downloadretry: Parameter(prefix='--')=None,
1210                 logfile: Parameter(prefix='--')=None,
1211                 debug: Parameter(prefix='--')=None,
1212                 niceness: Parameter(prefix='--')=None,
1213                 ping: Parameter(prefix='--')=None,
1214                 wu_filename: Parameter(prefix='--')=None,
1215                 arch: Parameter(prefix='--')=None,
1216                 certsha1: Parameter(prefix='--')=None,
1217                 **kwargs):
1218        if os.name == "nt":
1219            kwargs.setdefault("runprefix", "python3.exe")
1220        super().__init__(locals(), **kwargs)
1221
1222class SSH(Program):
1223    binary = "ssh"
1224    name = binary
1225    path = "/usr/bin"
1226    def __init__(self,
1227                 host: PositionalParameter(),
1228                 *args: PositionalParameter(),
1229                 compression: Toggle("C")=None,
1230                 verbose: Toggle("v")=None,
1231                 cipher: Parameter("c")=None,
1232                 configfile: Parameter("F")=None,
1233                 identity_file: Parameter("i")=None,
1234                 login_name: Parameter("l")=None,
1235                 port: Parameter("p")=None,
1236                 **kwargs):
1237        super().__init__(locals(), **kwargs)
1238
1239class RSync(Program):
1240    binary = "rsync"
1241    name = binary
1242    path = "/usr/bin"
1243    def __init__(self,
1244                 sourcefile: PositionalParameter(),
1245                 remotefile: PositionalParameter(),
1246                 **kwargs):
1247        super().__init__(locals(), **kwargs)
1248
1249class Ls(Program):
1250    binary = "ls"
1251    name = binary
1252    path = "/bin"
1253    def __init__(self,
1254                 *args : PositionalParameter(),
1255                 long : Toggle('l')=None,
1256                 **kwargs):
1257        super().__init__(locals(), **kwargs)
1258
1259class Kill(Program):
1260    binary = "kill"
1261    name = binary
1262    path = "/bin"
1263    def __init__(self,
1264                 *args: PositionalParameter(),
1265                 signal: Parameter("s"),
1266                 **kwargs):
1267        super().__init__(locals(), **kwargs)
1268
1269
1270if __name__ == '__main__':
1271    PROGRAM = Ls('foo', 'bar')
1272    CMDLINE = PROGRAM.make_command_line()
1273    print(CMDLINE)
1274