758 lines
20 KiB
Python
758 lines
20 KiB
Python
#!/usr/local/bin/python
|
|
|
|
from __future__ import print_function
|
|
|
|
"""A Python version of the Perforce "p4" client.
|
|
|
|
This uses the Python type P4API.P4Adapter, which is a wrapper for the
|
|
Perforce ClientApi object.
|
|
|
|
$Id: //depot/r12.2/p4-python/P4.py#1 $
|
|
|
|
#*******************************************************************************
|
|
# Copyright (c) 2007-2010, Perforce Software, Inc. All rights reserved.
|
|
# Portions Copyright (c) 1999, Mike Meyer. All rights reserved.
|
|
# Portions Copyright (c) 2004-2007, Robert Cowham. All rights reserved.
|
|
#
|
|
# Redistribution and use in source and binary forms, with or without
|
|
# modification, are permitted provided that the following conditions are met:
|
|
#
|
|
# 1. Redistributions of source code must retain the above copyright
|
|
# notice, this list of conditions and the following disclaimer.
|
|
#
|
|
# 2. Redistributions in binary form must reproduce the above copyright
|
|
# notice, this list of conditions and the following disclaimer in the
|
|
# documentation and/or other materials provided with the distribution.
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
# ARE DISCLAIMED. IN NO EVENT SHALL PERFORCE SOFTWARE, INC. BE LIABLE FOR ANY
|
|
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
#*******************************************************************************
|
|
|
|
Copyright 2007-2010 Perforce Software, Inc. All rights reserved
|
|
|
|
|
|
License:
|
|
See accompanying LICENSE.txt including for redistribution permission.
|
|
"""
|
|
|
|
import sys, os, string, datetime
|
|
import types, re
|
|
from contextlib import contextmanager
|
|
|
|
# P4Exception - some sort of error occurred
|
|
class P4Exception(Exception):
|
|
"""Exception thrown by P4 in case of Perforce errors or warnings"""
|
|
|
|
def __init__(self, value):
|
|
Exception.__init__(self)
|
|
|
|
if isinstance(value, (list, tuple)) and len(value) > 2:
|
|
self.value = value[0]
|
|
self.errors = value[1]
|
|
self.warnings = value[2]
|
|
else:
|
|
self.value = value
|
|
def __str__(self):
|
|
return str(self.value)
|
|
|
|
class Spec(dict):
|
|
"""Subclass of dict, representing the fields of a spec definition.
|
|
|
|
Attributes can be accessed either with the conventional dict format,
|
|
spec['attribute'] or with shorthand spec._attribute.
|
|
|
|
Instances of this class will preventing any unknown keys.
|
|
"""
|
|
def __init__(self, fieldmap=None):
|
|
self.__dict__['_Spec__fields'] = fieldmap
|
|
|
|
def permitted_fields(self):
|
|
return self.__fields
|
|
|
|
def __setitem__(self, key, value):
|
|
if not isinstance(value, str) and not isinstance(value, list):
|
|
raise P4Exception("Illegal value of type %s, must be string or list" % value.__class__)
|
|
if key in self or self.__fields == None:
|
|
dict.__setitem__(self, key, value)
|
|
elif str(key).lower() in self.__fields:
|
|
dict.__setitem__(self, self.__fields[key.lower()], value)
|
|
else:
|
|
raise P4Exception("Illegal field '%s'" % str(key))
|
|
|
|
def __getattr__(self, attr):
|
|
key = str(attr).lower()
|
|
if key[0] != '_':
|
|
raise AttributeError(attr)
|
|
key = key[1:]
|
|
if key in self:
|
|
return self[key]
|
|
elif key in self.__fields:
|
|
return self[self.__fields[key]]
|
|
|
|
def __setattr__(self, attr, value):
|
|
key = str(attr).lower()
|
|
if key[0] != '_':
|
|
raise AttributeError(attr)
|
|
key = key[1:]
|
|
self[key] = value
|
|
|
|
#
|
|
# P4Integration objects hold details about the integrations that have
|
|
# been performed on a particular revision. Used primarily with the
|
|
# P4Revision class
|
|
#
|
|
class Integration:
|
|
def __init__( self, how, file, srev, erev ):
|
|
self.how = how
|
|
self.file = file
|
|
self.srev = srev
|
|
self.erev = erev
|
|
|
|
def __repr__(self):
|
|
return "Integration (how = %s file = %s srev = %s erev = %s)" \
|
|
% (self.how, self.file, self.srev, self.erev)
|
|
#
|
|
# Each P4Revision object holds details about a particular revision
|
|
# of a file. It may also contain the history of any integrations
|
|
# to/from the file
|
|
#
|
|
|
|
class Revision:
|
|
def __init__( self, depotFile ):
|
|
self.depotFile = depotFile
|
|
self.integrations = []
|
|
self.rev = None
|
|
self.change = None
|
|
self.action = None
|
|
self.type = None
|
|
self.time = None
|
|
self.user = None
|
|
self.client = None
|
|
self.desc = None
|
|
self.digest = None
|
|
self.fileSize = None
|
|
|
|
def integration( self, how, file, srev, erev ):
|
|
rec = Integration( how, file, srev, erev )
|
|
self.integrations.append( rec )
|
|
return rec
|
|
|
|
# iterator over the collection calling a provided function
|
|
# Python's poor man version of the Ruby block
|
|
def each_integration(self):
|
|
for i in self.integrations:
|
|
yield i
|
|
|
|
def __repr__(self):
|
|
return "Revision (depotFile = %s rev = %s change = %s action = %s type = %s time = %s user = %s client = %s)" % \
|
|
(self.depotFile, self.rev, self.change, self.action, self.type, self.time, self.user, self.client)
|
|
#
|
|
# Each DepotFile entry contains details about one depot file.
|
|
#
|
|
class DepotFile:
|
|
def __init__( self, name ):
|
|
self.depotFile = name
|
|
self.revisions = []
|
|
|
|
def new_revision(self):
|
|
r = Revision( self.depotFile )
|
|
self.revisions.append( r )
|
|
return r
|
|
|
|
def each_revision(self):
|
|
for r in self.revisions:
|
|
yield r
|
|
|
|
def __repr__(self):
|
|
return "DepotFile (depotFile = %s, %s revisions)" % ( self.depotFile, len( self.revisions ) )
|
|
|
|
#
|
|
# Resolver class used in p4.run_resolve()
|
|
#
|
|
# The default simply checks that p4.input is set to sensible value
|
|
# This class is meant to be subclassed for a custom resolver and
|
|
# Resolver.resolve() overriden
|
|
#
|
|
|
|
class Resolver:
|
|
def __init__(self):
|
|
pass
|
|
|
|
def resolve(self, mergeInfo):
|
|
if mergeInfo.merge_hint == "e":
|
|
print("Standard resolver encountered merge conflict, skipping resolve")
|
|
return "s"
|
|
else:
|
|
return mergeInfo.merge_hint
|
|
|
|
def actionResolve(self, mergeInfo):
|
|
return mergeInfo.merge_hint
|
|
|
|
#
|
|
# OutputHandler base class
|
|
#
|
|
# Extend this class if you want to use the handler interface
|
|
#
|
|
|
|
class OutputHandler:
|
|
REPORT = 0
|
|
HANDLED = 1
|
|
CANCEL = 2
|
|
|
|
def __init__(self):
|
|
pass
|
|
|
|
def outputText(self, s):
|
|
return OutputHandler.REPORT
|
|
|
|
def outputBinary(self, b):
|
|
return OutputHandler.REPORT
|
|
|
|
def outputStat(self, h):
|
|
return OutputHandler.REPORT
|
|
|
|
def outputInfo(self, i):
|
|
return OutputHandler.REPORT
|
|
|
|
def outputMessage(self, e):
|
|
return OutputHandler.REPORT
|
|
|
|
class ReportHandler( OutputHandler ):
|
|
def __init__(self):
|
|
OutputHandler.__init__(self)
|
|
|
|
def outputText(self, s):
|
|
print( "text: ", s)
|
|
return OutputHandler.HANDLED
|
|
|
|
def outputBinary(self, b):
|
|
print( "binary: ", b)
|
|
return OutputHandler.HANDLED
|
|
|
|
def outputStat(self, h):
|
|
print( "stat:", h)
|
|
return OutputHandler.HANDLED
|
|
|
|
def outputInfo(self, i):
|
|
print( "info: ", i)
|
|
return OutputHandler.HANDLED
|
|
|
|
def outputMessage(self, e):
|
|
print( "error:", e)
|
|
return OutputHandler.HANDLED
|
|
|
|
class Progress:
|
|
def __init__(self):
|
|
pass
|
|
|
|
def init(self, type):
|
|
self.type = type
|
|
|
|
def setDescription( self, description, units ):
|
|
self.description = description
|
|
self.units = units
|
|
|
|
def setTotal( self, total ):
|
|
self.total = total
|
|
|
|
def update( self, position ):
|
|
self.position = position
|
|
|
|
def done( self, fail ):
|
|
pass
|
|
|
|
class TextProgress(Progress):
|
|
def __init__(self):
|
|
Progress.__init__(self)
|
|
|
|
def init(self, type):
|
|
Progress.init(self, type)
|
|
print( "Progress.init with %s" % type )
|
|
|
|
def setDescription(self, description, units):
|
|
Progress.setDescription(self, description, units)
|
|
print( "Progress.setDescription with %s and units %s" % (description, units) )
|
|
|
|
def setTotal( self, total ):
|
|
Progress.setTotal(self, total)
|
|
print( "Progress.setTotal with %s " % total )
|
|
|
|
def update( self, position ):
|
|
Progress.update(self, position )
|
|
print( "Progress.update with %s" % position )
|
|
|
|
def done( self, fail ):
|
|
Progress.done(self, fail)
|
|
print( "Progress.done with %s" % fail )
|
|
|
|
def processFilelog(h):
|
|
if "depotFile" in h:
|
|
df = DepotFile( h[ "depotFile" ] )
|
|
for n, rev in enumerate( h[ "rev" ]):
|
|
# Create a new revision of this file ready for populating
|
|
r = df.new_revision()
|
|
# Populate the base attributes of each revision
|
|
r.rev = int( rev )
|
|
r.change = int( h[ "change" ][ n ] )
|
|
r.action = h[ "action" ][ n ]
|
|
r.type = h[ "type" ][ n ]
|
|
r.time = datetime.datetime.utcfromtimestamp( int( h[ "time" ][ n ]) )
|
|
r.user = h[ "user" ][ n ]
|
|
r.client = h[ "client" ][ n ]
|
|
r.desc = h[ "desc" ][ n ]
|
|
if "digest" in h and n < len(h[ "digest" ]):
|
|
r.digest = h[ "digest" ][ n ]
|
|
if "fileSize" in h and n < len(h[ "fileSize" ]):
|
|
r.fileSize = h[ "fileSize" ][ n ]
|
|
|
|
# Now if there are any integration records for this revision,
|
|
# add them in too
|
|
|
|
if (not "how" in h) or (n >= len(h["how"]) or h["how"][n] == None):
|
|
continue
|
|
else:
|
|
for m, how in enumerate( h[ "how" ][ n ] ):
|
|
file = h[ "file" ][ n ][ m ]
|
|
srev = h[ "srev" ][ n ][ m ].lstrip('#')
|
|
erev = h[ "erev" ][ n ][ m ].lstrip('#')
|
|
|
|
if srev == "none":
|
|
srev = 0
|
|
else:
|
|
srev = int( srev )
|
|
|
|
if erev == "none":
|
|
erev = 0
|
|
else:
|
|
erev = int( erev )
|
|
|
|
r.integration( how, file, srev, erev )
|
|
return df
|
|
else:
|
|
raise Exception("Not a filelog object: " + h)
|
|
|
|
class FilelogOutputHandler(OutputHandler):
|
|
def __init__(self):
|
|
OutputHandler.__init__(self)
|
|
|
|
def outputStat(self, h):
|
|
df = processFilelog(h)
|
|
|
|
return self.outputFilelog(df)
|
|
|
|
def outputFilelog(self, f):
|
|
return OutputHandler.REPORT
|
|
|
|
# This is where the C/C++ shared library is loaded
|
|
# It has to be in this place because the library needs to access
|
|
# the classes defined above. Accessing classes defined below this
|
|
# entry would cause an endless loop
|
|
|
|
import P4API
|
|
#import p4_adsk.P4API as P4API
|
|
|
|
class P4(P4API.P4Adapter):
|
|
"""Use this class to communicate with a Perforce server
|
|
|
|
Instances of P4 will use the environment settings (including P4CONFIG)
|
|
to determine the connection parameters such as P4CLIENT and P4PORT.
|
|
|
|
This attributes can also be set separately before connecting.
|
|
|
|
To run any Perforce commands, users of this class first need to run
|
|
the connect() method.
|
|
|
|
It is good practice to disconnect() after the program is complete.
|
|
"""
|
|
# Constants useful for exception_level
|
|
# RAISE_ALL: Errors and Warnings are raised as exceptions (default)
|
|
# RAISE_ERROR: Only Errors are raised as exceptions
|
|
# RAISE_NONE: No exceptions are raised, instead False is returned
|
|
|
|
RAISE_ALL = 2
|
|
RAISE_ERROR = 1
|
|
RAISE_ERRORS = 1
|
|
RAISE_NONE = 0
|
|
|
|
# Named values for generic error codes returned by
|
|
# P4API.Message.generic
|
|
|
|
EV_NONE = 0 # misc
|
|
|
|
# The fault of the user
|
|
|
|
EV_USAGE = 0x01 # request not consistent with dox
|
|
EV_UNKNOWN = 0x02 # using unknown entity
|
|
EV_CONTEXT = 0x03 # using entity in wrong context
|
|
EV_ILLEGAL = 0x04 # trying to do something you can't
|
|
EV_NOTYET = 0x05 # something must be corrected first
|
|
EV_PROTECT = 0x06 # protections prevented operation
|
|
|
|
# No fault at all
|
|
|
|
EV_EMPTY = 0x11 # action returned empty results
|
|
|
|
# not the fault of the user
|
|
|
|
EV_FAULT = 0x21 # inexplicable program fault
|
|
EV_CLIENT = 0x22 # client side program errors
|
|
EV_ADMIN = 0x23 # server administrative action required
|
|
EV_CONFIG = 0x24 # client configuration inadequate
|
|
EV_UPGRADE = 0x25 # client or server too old to interact
|
|
EV_COMM = 0x26 # communications error
|
|
EV_TOOBIG = 0x27 # not even Perforce can handle this much
|
|
|
|
# Named values for error severities returned by
|
|
# P4API.Message.severity
|
|
E_EMPTY = 0 # nothing yet
|
|
E_INFO = 1 # something good happened
|
|
E_WARN = 2 # something not good happened
|
|
E_FAILED = 3 # user did something wrong
|
|
E_FATAL = 4 # system broken -- nothing can continue
|
|
|
|
# mappings for __iterate
|
|
# list-of-specs => ( name-of-one-spec, field-name-in-list-of-specs )
|
|
|
|
specfields = {
|
|
'clients' : ('client', 'client'),
|
|
'labels' : ('label', 'label'),
|
|
'branches' : ('branch', 'branch'),
|
|
'changes' : ('change', 'change'),
|
|
'streams' : ('stream', 'Stream'),
|
|
'jobs' : ('job', 'Job'),
|
|
'users' : ('user', 'User'),
|
|
'groups' : ('group', 'group'),
|
|
'depots' : ('depot', 'name'),
|
|
'servers' : ('server', 'Name')
|
|
}
|
|
|
|
def __init__(self, *args, **kwlist):
|
|
P4API.P4Adapter.__init__(self, *args, **kwlist)
|
|
|
|
def __del__(self):
|
|
if self.debug > 3:
|
|
print("P4.__del__()", file=sys.stderr)
|
|
|
|
# store the references to the created lambdas as a weakref to allow Python
|
|
# to clean up the garbage. |The lambda as a closure stores a reference to self
|
|
# which causes a circular reference problem without the weakref
|
|
|
|
def __getattr__(self, name):
|
|
if name.startswith("run_"):
|
|
cmd = name[len("run_"):]
|
|
return lambda *args, **kargs: self.run(cmd, *args, **kargs)
|
|
elif name.startswith("delete_"):
|
|
cmd = name[len("delete_"):]
|
|
return lambda *args, **kargs: self.run(cmd, "-d", *args, **kargs)
|
|
elif name.startswith("fetch_"):
|
|
cmd = name[len("fetch_"):]
|
|
return lambda *args, **kargs: self.run(cmd, "-o", *args, **kargs)[0]
|
|
elif name.startswith("save_"):
|
|
cmd = name[len("save_"):]
|
|
return lambda *args, **kargs: self.__save(cmd, *args, **kargs)
|
|
elif name.startswith("parse_"):
|
|
cmd = name[len("parse_"):]
|
|
return lambda *args, **kargs: self.parse_spec(cmd, *args, **kargs)
|
|
elif name.startswith("format_"):
|
|
cmd = name[len("format_"):]
|
|
return lambda *args, **kargs: self.format_spec(cmd, *args, **kargs)
|
|
elif name.startswith("iterate_"):
|
|
cmd = name[len("iterate_"):]
|
|
return lambda *args, **kargs: self.__iterate(cmd, *args, **kargs)
|
|
else:
|
|
raise AttributeError(name)
|
|
|
|
def __save(self, cmd, *args, **kargs):
|
|
self.input = args[0]
|
|
return self.run(cmd, "-i", args[1:], **kargs)
|
|
|
|
def __iterate(self, cmd, *args, **kargs):
|
|
|
|
specs = self.run(cmd, *args, **kargs)
|
|
if cmd in self.specfields:
|
|
spec = self.specfields[cmd][0]
|
|
field = self.specfields[cmd][1]
|
|
|
|
# Return a generators (Python iterator object)
|
|
# On iteration, this will retrieve one spec at a time
|
|
return ( self.run(spec, '-o', x[field])[0] for x in specs )
|
|
else:
|
|
raise Exception('Unknown spec list command: %s', cmd)
|
|
|
|
def __repr__(self):
|
|
state = "disconnected"
|
|
if self.connected():
|
|
state = "connected"
|
|
|
|
return "P4 [%s@%s %s] %s" % \
|
|
(self.user, self.client, self.port, state)
|
|
|
|
def identify(cls):
|
|
return P4API.identify()
|
|
identify = classmethod(identify)
|
|
|
|
def run(self, *args, **kargs):
|
|
"Generic run method"
|
|
context = {}
|
|
|
|
for (k,v) in list(kargs.items()):
|
|
context[k] = getattr(self, k)
|
|
setattr(self, k, v)
|
|
|
|
result = P4API.P4Adapter.run(self, *self.__flatten(args))
|
|
|
|
for (k,v) in list(context.items()):
|
|
setattr( self, k, v)
|
|
|
|
return result
|
|
|
|
def run_submit(self, *args, **kargs):
|
|
"Simplified submit - if any arguments is a dict, assume it to be the changeform"
|
|
nargs = list(args)
|
|
form = None
|
|
for n, arg in enumerate(nargs):
|
|
if isinstance( arg, dict):
|
|
self.input = arg
|
|
nargs.pop(n)
|
|
nargs.append("-i")
|
|
break
|
|
return self.run("submit", *nargs, **kargs)
|
|
|
|
def run_shelve(self, *args):
|
|
"Simplified shelve - if any arguments is a dict, assume it to be the changeform"
|
|
nargs = list(args)
|
|
form = None
|
|
for n, arg in enumerate(nargs):
|
|
if isinstance( arg, dict):
|
|
self.input = arg
|
|
nargs.pop(n)
|
|
nargs.append("-i")
|
|
break
|
|
return self.run("shelve", *nargs)
|
|
|
|
def delete_shelve(self, *args):
|
|
"Simplified deletion of shelves - if no -c is passed in, add it to the args"
|
|
nargs = list(args)
|
|
if '-c' not in nargs:
|
|
nargs = ['-c'] + nargs # prepend -c if it is not there
|
|
nargs = ['-d'] + nargs
|
|
return self.run("shelve", *nargs)
|
|
|
|
def run_login(self, *args):
|
|
"Simple interface to make login easier"
|
|
self.input = self.password
|
|
return self.run("login", *args)
|
|
|
|
def run_password( self, oldpass, newpass ):
|
|
"Simple interface to allow setting of the password"
|
|
if( oldpass and len(oldpass) > 0 ):
|
|
self.input = [ oldpass, newpass, newpass ]
|
|
else:
|
|
self.input = [ newpass, newpass ]
|
|
|
|
return self.run( "password" )
|
|
|
|
#
|
|
# run_filelog: convert "p4 filelog" responses into objects with useful
|
|
# methods
|
|
#
|
|
# Requires tagged output to be of any real use. If tagged output it not
|
|
# enabled then you just get the raw data back
|
|
#
|
|
def run_filelog( self, *args, **kargs ):
|
|
raw = self.run( 'filelog', args, **kargs )
|
|
if (not self.tagged or not raw):
|
|
# untagged mode returns simple strings, which breaks the code below
|
|
# raw could be None if a handler is used
|
|
return raw
|
|
result = []
|
|
for h in raw:
|
|
df = None
|
|
if isinstance( h, dict ):
|
|
df = processFilelog( h )
|
|
else:
|
|
df = h
|
|
result.append( df )
|
|
return result
|
|
|
|
def run_print(self, *args, **kargs):
|
|
raw = self.run('print', args, **kargs)
|
|
result = []
|
|
if raw:
|
|
for line in raw:
|
|
if isinstance(line, dict):
|
|
result.append(line)
|
|
result.append("")
|
|
else:
|
|
result[-1] += line
|
|
return result
|
|
else:
|
|
return []
|
|
|
|
def run_resolve(self, *args, **kargs):
|
|
if self.resolver:
|
|
myResolver = self.resolver
|
|
else:
|
|
myResolver = Resolver()
|
|
if "resolver" in kargs:
|
|
myResolver = kargs["resolver"]
|
|
|
|
savedResolver = self.resolver
|
|
self.resolver = myResolver
|
|
result = self.run("resolve", args)
|
|
self.resolver = savedResolver
|
|
|
|
return result
|
|
|
|
def run_tickets(self, *args):
|
|
fname = self.ticket_file
|
|
with open(fname) as f:
|
|
tickets_raw = f.readlines()
|
|
pattern = re.compile('([^=]*)=(.*):([^:]*)\n')
|
|
tickets = [ pattern.match(x).groups() for x in tickets_raw ]
|
|
keys = [ "Host", "User", "Ticket" ]
|
|
result = [ dict(zip(keys, x)) for x in tickets ]
|
|
return result
|
|
|
|
def __flatten(self, args):
|
|
result = []
|
|
if isinstance(args, tuple) or isinstance(args, list):
|
|
for i in args:
|
|
result.extend(self.__flatten(i))
|
|
else:
|
|
result.append(args)
|
|
return tuple(result)
|
|
|
|
def __enter__( self ):
|
|
return self
|
|
|
|
def __exit__( self, exc_type, exc_val, exc_tb ):
|
|
if self.connected():
|
|
self.disconnect()
|
|
return False
|
|
|
|
def connect( self ):
|
|
P4API.P4Adapter.connect( self )
|
|
return self
|
|
|
|
@contextmanager
|
|
def while_tagged( self, t ):
|
|
old = self.tagged
|
|
self.tagged = t
|
|
yield
|
|
self.tagged = old
|
|
|
|
@contextmanager
|
|
def at_exception_level( self, e ):
|
|
old = self.exception_level
|
|
self.exception_level = e
|
|
yield
|
|
self.exception_level = old
|
|
|
|
@contextmanager
|
|
def using_handler( self, c ):
|
|
old = self.handler
|
|
self.handler = c
|
|
yield
|
|
self.handler = old
|
|
|
|
@contextmanager
|
|
def saved_context( self , **kargs):
|
|
"""Saves the context of this p4 object and restores it again at the end of the block"""
|
|
|
|
saved_context = {}
|
|
for attr in self.__members__:
|
|
saved_context[attr] = getattr(self, attr)
|
|
|
|
for (k,v) in list(kargs.items()):
|
|
setattr( self, k, v)
|
|
|
|
yield
|
|
|
|
# now restore the context again. Ignore AttributeError exception
|
|
# Exception is expected because some attributes only have getters, no setters
|
|
|
|
for (k,v) in list(saved_context.items()):
|
|
try:
|
|
setattr( self, k, v )
|
|
except AttributeError:
|
|
pass # expected for server_level and p4config_file
|
|
|
|
class Map(P4API.P4Map):
|
|
def __init__(self, *args):
|
|
P4API.P4Map.__init__(self, *args)
|
|
if len(args) > 0:
|
|
self.insert( *args )
|
|
|
|
LEFT2RIGHT = True
|
|
RIGHT2LEFT = False
|
|
|
|
def __str__( self ):
|
|
result = ""
|
|
|
|
for a in self.as_array():
|
|
result += a + "\n"
|
|
|
|
return result
|
|
|
|
def is_empty(self):
|
|
"""Returns True if this map has no entries yet, otherwise False"""
|
|
|
|
return self.count() == 0
|
|
|
|
def includes(self, *args):
|
|
return self.translate(*args) != None
|
|
|
|
def reverse(self):
|
|
return Map(P4API.P4Map.reverse(self).as_array())
|
|
|
|
def insert(self, *args):
|
|
"""Insert an argument to the map. The argument can be:
|
|
|
|
A String,
|
|
Either of the form "[+-]//lhs/... //rhs/..." or "[+-]//lhs/..."
|
|
for label style maps.
|
|
A List:
|
|
This is a list of strings of one of the single string formats
|
|
described above.
|
|
A pair of Strings:
|
|
P4.Map.insert(lhs, rhs)
|
|
"""
|
|
|
|
if len(args) == 1 :
|
|
arg = args[0]
|
|
if isinstance( arg, str ):
|
|
P4API.P4Map.insert( self, arg )
|
|
elif isinstance( arg, list ):
|
|
for s in arg:
|
|
P4API.P4Map.insert( self, s )
|
|
|
|
else: # expecting 2 args in this case: left, right
|
|
left = args[0].strip()
|
|
right = args[1].strip()
|
|
P4API.P4Map.insert(self, left, right )
|
|
|
|
if __name__ == "__main__":
|
|
p4 = P4()
|
|
p4.connect()
|
|
try:
|
|
ret = p4.run(sys.argv[1:])
|
|
for line in ret:
|
|
if isinstance(line, dict):
|
|
print("-----")
|
|
for k in list(line.keys()):
|
|
print(k, "=", line[k])
|
|
else:
|
|
print(line)
|
|
except:
|
|
for e in p4.errors:
|
|
print(e)
|