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