Changeset 3376:608a856db9fe in livinglogic.python.xist

Show
Ignore:
Timestamp:
06/16/08 20:52:23 (11 years ago)
Author:
Walter Doerwald <walter@…>
Branch:
default
Message:

Add ULLDumpAction and ULLLoadAction. Allow action objects for most action attributes.

Files:
2 modified

Legend:

Unmodified
Added
Removed
  • NEWS.rst

    r3375 r3376  
    1212 
    1313*   :mod:`ll.make` has gained new actions: :class:`GZipAction`, 
    14     :class:`GUnzipAction`, :class:`CallFuncAction`,  :class:`CallMethAction` and 
    15     :class:`ULLCompileAction`. 
     14    :class:`GUnzipAction`, :class:`CallFuncAction`,  :class:`CallMethAction`, 
     15    :class:`ULLCompileAction`, :class:`ULLDumpAction` and :class:`ULLLoadAction`. 
    1616 
    1717*   All actions in :mod:`ll.make` no longer check whether their inputs are 
    1818    action objects. Non-action objects are simply treated as ancient input data. 
     19 
     20*   Most attributes of action objects in :mod:`ll.make` can now be action objects 
     21    themselves, so for example the name of the encoding to be used in an 
     22    :class:`EncodeAction` can be the output of another action. 
    1923 
    2024*   :func:`ll.misc.xmlescape` now escapes ``'`` as ``&#39;`` for IE compatibility. 
     
    4751 
    4852*   Python versions of all the functions in the module :mod:`ll.misc` have been 
    49     added that will be used, in case the C module is not available. 
     53    added that will be used in case the C module is not available. 
    5054 
    5155 
  • src/ll/make.py

    r3372 r3376  
    413413    the input data into output data. 
    414414    """ 
    415     def __init__(self): 
     415    def __init__(self, input=None): 
    416416        Action.__init__(self) 
    417         self.input = None 
     417        self.input = input 
    418418 
    419419    def __rdiv__(self, input): 
    420420        """ 
    421421        Register the action :var:`input` as the input action for :var:`self` and 
    422         return :var:`self` (which enables chaining :class:`PipeAction` objects). 
     422        return :var:`self` (which enables "chaining" :class:`PipeAction` objects). 
    423423        """ 
    424424        self.input = input 
     
    451451    input data unmodified, but updates a number of other actions in the process. 
    452452    """ 
    453     def __init__(self, *inputs): 
    454         PipeAction.__init__(self) 
    455         self.inputs = list(inputs) 
    456  
    457     def addinputs(self, *inputs): 
    458         """ 
    459         Register all actions in :var:`inputs` as additional actions that have 
     453    def __init__(self, input, *otherinputs): 
     454        PipeAction.__init__(self, input) 
     455        self.otherinputs = list(otherinputs) 
     456 
     457    def addinputs(self, *otherinputs): 
     458        """ 
     459        Register all actions in :var:`otherinputs` as additional actions that have 
    460460        to be updated before :var:`self` is updated. 
    461461        """ 
    462         self.inputs.extend(inputs) 
     462        self.otherinputs.extend(otherinputs) 
    463463        return self 
    464464 
    465465    def __iter__(self): 
    466466        yield self.input 
    467         for input in self.inputs: 
     467        for input in self.otherinputs: 
    468468            yield input 
    469469 
     
    473473        havedata = False 
    474474        changedinputs = bigbang 
    475         for item in self.inputs: 
     475        for item in self.otherinputs: 
    476476            (data, changed) = getoutputs(project, since, item) 
    477477            changedinputs = max(changedinputs, changed) 
     
    549549    objects providing the appropriate interface). 
    550550    """ 
    551     def __init__(self, key): 
     551    def __init__(self, input=None, key=None): 
    552552        """ 
    553553        Create a :class:`FileAction` object with :var:`key` as the "filename". 
    554554        :var:`key` must be an object that provides a method :meth:`open` for 
    555         opening readable and writable streams to the file. 
     555        opening readable and writable streams to the file. :var:`input` is the 
     556        data written to the file (or the action producing the data). 
    556557         
    557558        """ 
    558         PipeAction.__init__(self) 
     559        PipeAction.__init__(self, input) 
    559560        self.key = key 
    560561        self.buildno = None 
     
    630631    This action pickles the input data into a string. 
    631632    """ 
    632     def __init__(self, protocol=0): 
     633    def __init__(self, input=None, protocol=0): 
    633634        """ 
    634635        Create a new :class:`PickleAction` instance. :var:`protocol` is used as 
    635636        the pickle protocol. 
    636637        """ 
    637         PipeAction.__init__(self) 
     638        PipeAction.__init__(self, input) 
    638639        self.protocol = protocol 
    639640 
    640     def execute(self, project, data): 
    641         project.writestep(self, "Unpickling") 
    642         return cPickle.dumps(data, self.protocol) 
     641    @report 
     642    def get(self, project, since): 
     643        (data, self.changed) = getoutputs(project, since, (self.input, self.protocol)) 
     644        if data is not nodata: 
     645            project.writestep(self, "Pickling with protocol %r" % data[1]) 
     646            data = cPickle.dumps(data[0], data[1]) 
     647        return data 
    643648 
    644649    def __repr__(self): 
    645         return "<%s.%s object with protocol=%r at 0x%x>" % (self.__class__.__module__, self.__class__.__name__, self.protocol, id(self)) 
     650        if isinstance(self.protocol, Action): 
     651            return "<%s.%s object at 0x%x>" % (self.__class__.__module__, self.__class__.__name__, id(self)) 
     652        else: 
     653            return "<%s.%s object with protocol=%r at 0x%x>" % (self.__class__.__module__, self.__class__.__name__, self.protocol, id(self)) 
    646654 
    647655 
    648656class JoinAction(Action): 
    649657    """ 
    650     This action joins the input of all its input actions. 
     658    This action joins the input of all its input actions into one string. 
    651659    """ 
    652660    def __init__(self, *inputs): 
     
    669677        (data, self.changed) = getoutputs(project, since, self.inputs) 
    670678        if data is not nodata: 
    671             project.writestep(self, "Joining data") 
     679            project.writestep(self, "Joining data from %d inputs" % len(data)) 
    672680            data = "".join(data) 
    673681        return data 
     
    707715    round. 
    708716    """ 
    709     def __init__(self): 
    710         PipeAction.__init__(self) 
     717    def __init__(self, input=None): 
     718        PipeAction.__init__(self, input) 
    711719        self.since = bigcrunch 
    712720        self.data = nodata 
     
    729737    """ 
    730738 
    731     def __init__(self, attrname): 
    732         PipeAction.__init__(self) 
     739    def __init__(self, input=None, attrname=None): 
     740        PipeAction.__init__(self, input) 
    733741        self.attrname = attrname 
    734742 
    735     def execute(self, project, data): 
    736         project.writestep(self, "Getting attribute ", self.attrname) 
    737         return getattr(data, self.attrname) 
     743    @report 
     744    def get(self, project, since): 
     745        (data, self.changed) = getoutputs(project, since, (self.input, self.attrname) 
     746        if data is not nodata: 
     747            project.writestep(self, "Getting attribute ", self.attrname) 
     748            data = getattr(data[0], data[1]) 
     749        return data 
    738750 
    739751    def __repr__(self): 
    740         return "<%s.%s object with attrname=%r at 0x%x>" % (self.__class__.__module__, self.__class__.__name__, self.attrname, id(self)) 
     752        if isinstance(self.attrname, Action): 
     753            return "<%s.%s object at 0x%x>" % (self.__class__.__module__, self.__class__.__name__, id(self)) 
     754        else: 
     755            return "<%s.%s object with attrname=%r at 0x%x>" % (self.__class__.__module__, self.__class__.__name__, self.attrname, id(self)) 
    741756 
    742757 
     
    796811    """ 
    797812 
    798     def __init__(self, builder=None, pool=None, base=None): 
     813    def __init__(self, input=None, builder=None, pool=None, base=None): 
    799814        """ 
    800815        Create an :class:`XISTParseAction` object. :var:`builder` must be an 
    801816        instance of :class:`ll.xist.parsers.Builder`. If :var:`builder` is 
    802817        :const:`None` a builder will be created for you. :var:`pool` must be an 
    803         action that returns an XIST pool object. :var:`base` will be the base 
    804         URL used for parsing. 
    805         """ 
    806         PipeAction.__init__(self) 
     818        XIST pool object (or an action returning one). :var:`base` will be the 
     819        base URL used for parsing. 
     820        """ 
     821        PipeAction.__init__(self, input) 
    807822        if builder is None: 
    808823            from ll.xist import parsers 
     
    818833    @report 
    819834    def get(self, project, since): 
    820         (data, self.changed) = getoutputs(project, since, (self.pool, self.input)) 
     835        (data, self.changed) = getoutputs(project, since, (self.input, self.builder, self.pool, self.base)) 
    821836 
    822837        if data is not nodata: 
    823838            # We really have to do some work 
    824839            from ll.xist import xsc 
    825             (pool, data) = data 
    826             oldpool = self.builder.pool 
     840            (data, builder, pool, base) = data 
     841            oldpool = builder.pool 
    827842            try: 
    828                 self.builder.pool = xsc.Pool(pool, oldpool) 
    829  
    830                 project.writestep(self, "Parsing XIST input with base ", self.base) 
    831                 data = self.builder.parsestring(data, self.base) 
     843                builder.pool = xsc.Pool(pool, oldpool) 
     844 
     845                project.writestep(self, "Parsing XIST input with base ", base) 
     846                data = builder.parsestring(data, base) 
    832847            finally: 
    833                 self.builder.pool = oldpool # Restore old pool 
     848                builder.pool = oldpool # Restore old pool 
    834849        return data 
    835850 
     
    843858    """ 
    844859 
    845     def __init__(self, mode=None, target=None, stage=None, lang=None, targetroot=None): 
    846         """ 
    847         Create a new :class:`XISTConvertAction` object. The arguments will be 
    848         used to create a :class:`ll.xist.converters.Converter` object for each 
    849         call to :meth:`execute`. 
    850         """ 
    851         PipeAction.__init__(self) 
     860    def __init__(self, input=None, mode=None, target=None, stage=None, lang=None, targetroot=None): 
     861        """ 
     862        Create a new :class:`XISTConvertAction` object. :var:`input` is the input 
     863        none (or an action producing a node). The other arguments will be used to 
     864        create a :class:`ll.xist.converters.Converter` object for each call to 
     865        :meth:`get`. 
     866         
     867        During conversion the :attr:`makeaction` attribute of the converter will 
     868        be set to :var:`self` and the :attr:`makeproject` attribute will be set 
     869        to the project. 
     870        """ 
     871        PipeAction.__init__(self, input) 
    852872        self.mode = mode 
    853873        self.target = target 
     
    856876        self.targetroot = targetroot 
    857877 
    858     def converter(self, project): 
    859         """ 
    860         Create a new :class:`ll.xist.converters.Converter` object to be used by 
    861         this action. The attributes of this new converter (:attr:`mode`, 
    862         :attr:`target`, :attr:`stage`, etc.) will correspond to those specified 
    863         in the constructor. 
    864  
    865         The :attr:`makeaction` attribute of the converter will be set to 
    866         :var:`self` and the :attr:`makeproject` attribute will be set to 
    867         :var:`project`. 
    868         """ 
    869         from ll.xist import converters 
    870         return converters.Converter(root=self.targetroot, mode=self.mode, stage=self.stage, target=self.target, lang=self.lang, makeaction=self, makeproject=project) 
    871  
    872     def execute(self, project, data): 
    873         """ 
    874         Convert the XIST node :var:`data` using a converter provided by 
    875         :meth:`converter` and return the converted node. 
    876         """ 
    877         args = [] 
    878         for argname in ("mode", "target", "stage", "lang", "targetroot"): 
    879             arg = getattr(self, argname, None) 
    880             if arg is not None: 
    881                 args.append("%s=%r" % (argname, arg)) 
    882         if args: 
    883             args = " with %s" % ", ".join(args) 
    884         else: 
    885             args = "" 
    886         project.writestep(self, "Converting XIST node", args) 
    887         return data.convert(self.converter(project)) 
     878    @report 
     879    def get(self, project, since): 
     880        (data, self.changed) = getoutputs(project, since, (self.input, dict(mode=self.mode, target=self.target, stage=self.stage, lang=self.lang, targetroot=self.targetroot)) 
     881 
     882        if data is not nodata: 
     883            from ll.xist import converters 
     884            args = [] 
     885            for argname in ("mode", "target", "stage", "lang", "targetroot"): 
     886                arg = data[1][argname] 
     887                if arg is not None: 
     888                    args.append("%s=%r" % (argname, arg)) 
     889            if args: 
     890                args = " with %s" % ", ".join(args) 
     891            else: 
     892                args = "" 
     893            project.writestep(self, "Converting XIST node", args) 
     894            converter = converters.Converter(makeaction=self, makeproject=project, **data[1]) 
     895            data = data[0].convert(converter) 
     896        return data 
    888897 
    889898    def __repr__(self): 
     
    891900        for argname in ("mode", "target", "stage", "lang", "targetroot"): 
    892901            arg = getattr(self, argname, None) 
    893             if arg is not None: 
     902            if arg is not None and not isinstance(arg, Action): 
    894903                args.append("%s=%r" % (argname, arg)) 
    895904        if args: 
     
    905914    """ 
    906915 
    907     def __init__(self, publisher=None, base=None): 
     916    def __init__(self, input=None, publisher=None, base=None): 
    908917        """ 
    909918        Create an :class:`XISTPublishAction` object. :var:`publisher` must be an 
     
    912921        the base URL used for publishing. 
    913922        """ 
    914         PipeAction.__init__(self) 
    915         if publisher is None: 
    916             from ll.xist import publishers 
    917             publisher = publishers.Publisher() 
     923        PipeAction.__init__(self, input) 
    918924        self.publisher = publisher 
    919925        self.base = base 
    920926 
    921     def execute(self, project, data): 
    922         """ 
    923         Use the publisher specified in the constructor to publish the input XIST 
    924         node :var:`data`. The output data is the generated XML string. 
    925         """ 
    926         project.writestep(self, "Publishing XIST node with base ", self.base) 
    927         return "".join(self.publisher.publish(data, self.base)) 
     927    @report 
     928    def get(self, project, since): 
     929        (data, self.changed) = getoutputs(project, since, (self.input, self.publisher, self.base) 
     930 
     931        if data is not nodata: 
     932            project.writestep(self, "Publishing XIST node with base ", data[2]) 
     933            publisher = data[1] 
     934            if publisher is None: 
     935                from ll.xist import publishers 
     936                publisher = publishers.Publisher() 
     937            data = "".join(publisher.publish(data[0], data[2])) 
     938        return data 
    928939 
    929940    def __repr__(self): 
    930         return "<%s.%s object with base=%r at 0x%x>" % (self.__class__.__module__, self.__class__.__name__, self.base, id(self)) 
     941        if isinstance(self.base, Action): 
     942            return "<%s.%s object at 0x%x>" % (self.__class__.__module__, self.__class__.__name__, id(self)) 
     943        else: 
     944            return "<%s.%s object with base=%r at 0x%x>" % (self.__class__.__module__, self.__class__.__name__, self.base, id(self)) 
    931945 
    932946 
     
    935949    This action creates a plain text version of an HTML XIST node. 
    936950    """ 
    937     def __init__(self, encoding="iso-8859-1", width=72): 
    938         PipeAction.__init__(self) 
     951    def __init__(self, input=None, encoding="iso-8859-1", width=72): 
     952        PipeAction.__init__(self, input) 
    939953        self.encoding = encoding 
    940954        self.width = width 
    941955 
    942     def execute(self, project, data): 
    943         project.writestep(self, "Converting XIST node to text with encoding=%r, width=%r" % (self.encoding, self.width)) 
    944         from ll.xist.ns import html 
    945         return html.astext(data, encoding=self.encoding, width=self.width) 
     956    @report 
     957    def get(self, project, since): 
     958        (data, self.changed) = getoutputs(project, since, (self.input, self.encoding, self.width) 
     959 
     960        if data is not nodata: 
     961            project.writestep(self, "Converting XIST node to text with encoding=%r, width=%r" % (data[1], data[2])) 
     962            from ll.xist.ns import html 
     963            data = html.astext(data[0], encoding=data[1], width=data[2]) 
     964        return data 
     965 
     966    def __repr__(self): 
     967        args = [] 
     968        for argname in ("encoding", "width"): 
     969            arg = getattr(self, argname, None) 
     970            if arg is not None and not isinstance(arg, Action): 
     971                args.append("%s=%r" % (argname, arg)) 
     972        if args: 
     973            args = " with %s" % ", ".join(args) 
     974        else: 
     975            args = "" 
     976        return "<%s.%s object%s at 0x%x>" % (self.__class__.__module__, self.__class__.__name__, "".join(args), id(self)) 
    946977 
    947978 
     
    9791010    """ 
    9801011 
    981     def __init__(self, encoding=None): 
     1012    def __init__(self, input=None, encoding=None): 
    9821013        """ 
    9831014        Create a :class:`DecodeAction` object with :var:`encoding` as the name of 
     
    9851016        encoding will be used. 
    9861017        """ 
    987         PipeAction.__init__(self) 
     1018        PipeAction.__init__(self, input) 
    9881019        if encoding is None: 
    9891020            encoding = sys.getdefaultencoding() 
    9901021        self.encoding = encoding 
    9911022 
    992     def execute(self, project, data): 
    993         project.writestep(self, "Decoding input with encoding ", self.encoding) 
    994         return data.decode(self.encoding) 
     1023    @report 
     1024    def get(self, project, since): 
     1025        (data, self.changed) = getoutputs(project, since, (self.input, self.encoding) 
     1026 
     1027        if data is not nodata: 
     1028            project.writestep(self, "Decoding input with encoding ", data[1]) 
     1029            data = data[0].decode(data[1]) 
     1030        return data 
    9951031 
    9961032    def __repr__(self): 
    997         return "<%s.%s object encoding=%r at 0x%x>" % (self.__class__.__module__, self.__class__.__name__, self.encoding, id(self)) 
     1033        if isinstance(self.encoding, Action): 
     1034            return "<%s.%s object at 0x%x>" % (self.__class__.__module__, self.__class__.__name__, id(self)) 
     1035        else: 
     1036            return "<%s.%s object encoding=%r at 0x%x>" % (self.__class__.__module__, self.__class__.__name__, self.encoding, id(self)) 
    9981037 
    9991038 
     
    10041043    """ 
    10051044 
    1006     def __init__(self, encoding=None): 
     1045    def __init__(self, input=None, encoding=None): 
    10071046        """ 
    10081047        Create an :class:`EncodeAction` object with :var:`encoding` as the name 
     
    10101049        encoding will be used. 
    10111050        """ 
    1012         PipeAction.__init__(self) 
     1051        PipeAction.__init__(self, input) 
    10131052        if encoding is None: 
    10141053            encoding = sys.getdefaultencoding() 
    10151054        self.encoding = encoding 
    10161055 
    1017     def execute(self, project, data): 
    1018         project.writestep(self, "Encoding input with encoding ", self.encoding) 
    1019         return data.encode(self.encoding) 
     1056    @report 
     1057    def get(self, project, since): 
     1058        (data, self.changed) = getoutputs(project, since, (self.input, self.encoding) 
     1059 
     1060        if data is not nodata: 
     1061            project.writestep(self, "Encoding input with encoding ", data[1]) 
     1062            data = data[0].encode(data[1]) 
     1063        return data 
    10201064 
    10211065    def __repr__(self): 
    1022         return "<%s.%s object encoding=%r at 0x%x>" % (self.__class__.__module__, self.__class__.__name__, self.encoding, id(self)) 
     1066        if isinstance(self.encoding, Action): 
     1067            return "<%s.%s object at 0x%x>" % (self.__class__.__module__, self.__class__.__name__, id(self)) 
     1068        else: 
     1069            return "<%s.%s object encoding=%r at 0x%x>" % (self.__class__.__module__, self.__class__.__name__, self.encoding, id(self)) 
    10231070 
    10241071 
     
    10391086    compressed output object. 
    10401087    """ 
    1041     def __init__(self, compresslevel=9): 
    1042         PipeAction.__init__(self) 
     1088    def __init__(self, input=None, compresslevel=9): 
     1089        PipeAction.__init__(self, input) 
    10431090        self.compresslevel = compresslevel 
    10441091 
    1045     def execute(self, project, data): 
    1046         project.writestep(self, "Compressing input with level %d" % self.compresslevel) 
    1047         import gzip, cStringIO 
    1048         stream = cStringIO.StringIO() 
    1049         compressor = gzip.GzipFile(filename="", mode="wb", fileobj=stream, compresslevel=self.compresslevel) 
    1050         compressor.write(data) 
    1051         compressor.close() 
    1052         return stream.getvalue() 
     1092    @report 
     1093    def get(self, project, since): 
     1094        (data, self.changed) = getoutputs(project, since, (self.input, self.compresslevel) 
     1095 
     1096        if data is not nodata: 
     1097            project.writestep(self, "Compressing input with level %d" % data[1]) 
     1098            import gzip, cStringIO 
     1099            stream = cStringIO.StringIO() 
     1100            compressor = gzip.GzipFile(filename="", mode="wb", fileobj=stream, compresslevel=data[1]) 
     1101            compressor.write(data[0]) 
     1102            compressor.close() 
     1103            data = stream.getvalue() 
     1104        return data 
    10531105 
    10541106 
     
    10901142    """ 
    10911143    This action calls a method of an object with a number of arguments. Both 
    1092     positional and keyword arguments are supported and the object, the method name 
    1093     and the arguments can be static objects or actions. 
     1144    positional and keyword arguments are supported and the object, the method 
     1145    name and the arguments can be static objects or actions. 
    10941146    """ 
    10951147    def __init__(self, obj, methname, *args, **kwargs): 
     
    12021254 
    12031255 
     1256class ULLDumpAction(PipeAction): 
     1257    """ 
     1258    This action dumps an :class:`ll.ullc.Template` object into a string. 
     1259    """ 
     1260 
     1261    def execute(self, project, data): 
     1262        project.writestep(self, "Dumping ULL template") 
     1263        return data.dumps() 
     1264 
     1265 
     1266class ULLLoadAction(PipeAction): 
     1267    """ 
     1268    This action loads a :class:`ll.ullc.Template` object from a string. 
     1269    """ 
     1270 
     1271    def execute(self, project, data): 
     1272        project.writestep(self, "Loading ULL template") 
     1273        from ll import ullc 
     1274        return ullcs.loads(data) 
     1275 
     1276 
    12041277class CommandAction(PipeAction): 
    12051278    """ 
     
    12291302    """ 
    12301303 
    1231     def __init__(self, mode=0644): 
     1304    def __init__(self, input=None, mode=0644): 
    12321305        """ 
    12331306        Create an :class:`ModeAction` object. :var:`mode` (which defaults to 
    12341307        :const:`0644`) will be use as the permission bit pattern. 
    12351308        """ 
    1236         PipeAction.__init__(self) 
     1309        PipeAction.__init__(self, input) 
    12371310        self.mode = mode 
    12381311 
    1239     def execute(self, project, data): 
     1312    @report 
     1313    def get(self, project, since): 
    12401314        """ 
    12411315        Change the permission bits of the file ``self.getkey()``. 
    12421316        """ 
    1243         key = self.getkey() 
    1244         project.writestep(self, "Changing mode of ", project.strkey(key), " to 0%03o" % self.mode) 
    1245         key.chmod(self.mode) 
     1317        (data, self.changed) = getoutputs(project, since, (self.input, self.mode)) 
     1318        if data is not nodata: 
     1319            key = self.getkey() 
     1320            project.writestep(self, "Changing mode of ", project.strkey(key), " to 0%03o" % data[1]) 
     1321            key.chmod(data[1]) 
     1322            data = data[0] 
     1323        return data 
    12461324 
    12471325    def __repr__(self): 
    1248         return "<%s.%s object mode=0%03o at 0x%x>" % (self.__class__.__module__, self.__class__.__name__, self.mode, id(self)) 
     1326        if isinstance(self.mode, Action): 
     1327            return "<%s.%s object at 0x%x>" % (self.__class__.__module__, self.__class__.__name__, id(self)) 
     1328        else: 
     1329            return "<%s.%s object mode=0%03o at 0x%x>" % (self.__class__.__module__, self.__class__.__name__, self.mode, id(self)) 
    12491330 
    12501331 
     
    12661347        self.group = group 
    12671348 
    1268     def execute(self, project, data): 
     1349    @report 
     1350    def get(self, project, since): 
    12691351        """ 
    12701352        Change the ownership of the file ``self.getkey()``. 
    12711353        """ 
    1272         key = self.getkey() 
    1273         project.writestep(self, "Changing owner of ", project.strkey(key), " to ", self.user, " and group to ", self.user) 
    1274         key.chown(self.user, self.group) 
     1354        (data, self.changed) = getoutputs(project, since, (self.input, self.user, self.group)) 
     1355        if data is not nodata: 
     1356            key = self.getkey() 
     1357            project.writestep(self, "Changing owner of ", project.strkey(key), " to ", data[1], " and group to ", data[2]) 
     1358            key.chown(data[0], data[1]) 
     1359            data = data[0] 
     1360        return data 
    12751361 
    12761362    def __repr__(self): 
    1277         return "<%s.%s object user=%r group=%r at 0x%x>" % (self.__class__.__module__, self.__class__.__name__, self.user, self.group, id(self)) 
     1363        args = [] 
     1364        for argname in ("user", "group"): 
     1365            arg = getattr(self, argname, None) 
     1366            if arg is not None and not isinstance(arg, Action): 
     1367                args.append("%s=%r" % (argname, arg)) 
     1368        if args: 
     1369            args = " with %s" % ", ".join(args) 
     1370        else: 
     1371            args = "" 
     1372        return "<%s.%s object%s at 0x%x>" % (self.__class__.__module__, self.__class__.__name__, "".join(args), id(self)) 
    12781373 
    12791374