#!/usr/local/bin/python """A Python version of the Perforce "p4" client. This uses the Python type P4Client, which is a wrapper for the Perforce ClientApi object. It includes the "ClientUser" class, which is a Python version of the default ClientUser from Perforce. $Id: //guest/robert_cowham/perforce/API/python/main/p4.py#13 $ Portions Copyright 1999, Mike Meyer. All rights reserved. Portions Copyright 2004-2007, Robert Cowham. All rights reserved. License: See accompanying LICENSE.txt including for redistribution permission. """ import sys, os, string import P4Client import types # P4Error - some sort of error occurred class P4Error(Exception): def __init__(self, value): self.value = value def __str__(self): return str(self.value) class P4ClientHandler: """Class to wrap P4Client.""" p4attributes = ["charset", "client", "config", "cwd", "host", "language", "os", "p4_api_ver", "password", "port", "prog", "ticket_file", "user", "version"] connected = 0 exception_level = 1 # raise exceptions on errors but not warnings tagged = 0 parse_forms = 0 def __init__(self, **options): "Create the P4Client object I'm wrapping." self.p4c = P4Client.P4Client(self) self._reset() def __getattr__(self, name): "Handles requests for attributes." # Setup various dict stuff for use in run function if name in self.p4attributes: return getattr(self.p4c, name) elif name.startswith('fetch_'): self.__dict__["_fetch"] = name[len('fetch_'):] return self.run elif name.startswith('save_'): self.__dict__["_save"] = name[len('save_'):] return self.run elif name.startswith('delete_'): self.__dict__["_delete"] = name[len('delete_'):] return self.run elif name.startswith('run_'): self.__dict__["_run"] = name[len('run_'):] return self.run elif name == 'login': self.__dict__["_login"] = 1 return self.run elif name == 'passwd': self.__dict__["_passwd"] = 1 return self.run elif not self.__dict__.has_key(name): raise AttributeError else: return self.__dict__[name] def __setattr__(self, name, value): if name in self.p4attributes: setattr(self.p4c, name, value) else: self.__dict__[name] = value def connect(self): "Connect to the P4Client." self.connected = 1 try: result = self.p4c.Init() except P4Client.error, e: raise P4Error(str(e)) except: raise return result def dropped(self): "Check to see if the P4Client connection has been dropped." return self.p4c.Dropped() def disconnect(self): "Close the connection to the P4Client." self.connected = 0 try: result = self.p4c.Final() except P4Client.error, e: raise P4Error(str(e)) except: raise return result def _reset(self): self.output = [] self.warnings = [] self.errors = [] def tagged(self): "Set tag protocol" self.tagged = 1 self.p4c.SetProtocol("tag", "") def translate(self, output, content=-2, fnames=-2, dialog=-2): "Set translation options for international servers - see P4 class constants" self.p4c.SetTrans(output, content, fnames, dialog) def parse_forms(self): "Set appropriate protocol" self.tagged() self.parse_forms = 1 self.p4c.SetProtocol("specstring", "") def set_protocol(self, var, value): "Directly set protocol values" self.p4c.SetProtocol(var, value) def _flatten(self, args): "Flatten potentially recursive tuples/lists into one list." result = [] if len(args): if isinstance(args[0], tuple) or isinstance(args[0], list): for i in range(len(args)): result.extend(self._flatten(args[i])) else: result.extend(args) return result def _get_cmd(self, key): cmd = self.__dict__[key] del self.__dict__[key] return cmd def run(self, *args): "Run the given command, parsing output with the named parser." if not self.connected: msg = "Can't run a command without running connect() first" self.errors.append(msg) if self.exception_level > 0: raise P4Error(msg) return [] self._reset() # Check if we have been called via special fetch_/save_ methods pop_output = False cmd_args = self._flatten(args) if self.__dict__.has_key("_fetch"): pop_output = True cmd = self._get_cmd("_fetch") cmd_args.insert(0, "-o") elif self.__dict__.has_key("_save"): cmd = self._get_cmd("_save") self.__dict__["input"] = cmd_args.pop() cmd_args.insert(0, "-i") elif self.__dict__.has_key("_delete"): pop_output = True cmd = self._get_cmd("_delete") cmd_args.insert(0, "-d") elif self.__dict__.has_key("_run"): cmd = self._get_cmd("_run") elif self.__dict__.has_key("_login"): cmd = "login" if len(cmd_args) > 0: self.__dict__["input"] = cmd_args.pop() del self.__dict__["_login"] elif self.__dict__.has_key("_passwd"): cmd = "passwd" if len(cmd_args) > 0: self.__dict__["input"] = cmd_args.pop() del self.__dict__["_passwd"] else: cmd = cmd_args.pop(0) try: self.p4c.SetArgv(cmd_args) except P4Client.error, e: raise P4Error(str(e)) except: raise self.p4c.Run(cmd) if len(self.errors) > 0 and self.exception_level > 0: raise P4Error(string.join(self.errors, "\n")) if len(self.warnings) > 0 and self.exception_level > 1: raise P4Error(string.join(self.warnings, "\n")) if self.p4c.Dropped(): raise P4Error("Connection dropped") if pop_output: return self.output[0] else: return self.output def InputData(self): "Return any input that the user has provided to API." if not self.__dict__.has_key("input"): # Save error for later use self.errors.append("No input provided and API wanted it") return None return self.input def Prompt(self, msg): "Return any input that the user has provided to API." # Used for passwords etc. if not self.__dict__.has_key("input"): # Save error for later use self.errors.append("No input provided and API wanted it") return None return self.input def OutputInfo(self, data, level): "Parse the given data, adding it to self data." for line in string.split(data, '\n'): self.output.append(line) def OutputText(self, text): "Adds text lines to the output data." for line in string.split(text, '\n'): self.output.append(line) def OutputBinary(self, text): "Adds binary lines to the output data." self.output.append(text) def StartDict(self): "Create new dictionary into which items will be put" self._dict = {} def _array_insert(self, arr, index, val): "Recursively insert into array." if len(index) == 1: while len(arr) < index[0]: arr.append(None) arr.append(val) else: while len(arr) < index[0]: arr.append(None) if len(arr) == index[0]: arr.append([]) self._array_insert(arr[index[0]], index[1:], val) def InsertItem(self, base, index, val): "Handle outputting of items into an array" if len(index): if not self._dict.has_key(base): self._dict[base] = [] arr = self._dict[base] # Handle strange things like diff2: depotFile and depotFile2 if type(arr) == types.StringType: self._dict[base + index] = val else: ## print base, index, val indices = map(int, string.split(index, ',')) self._array_insert(arr, indices, val) else: if self._dict.has_key(base): key = base + "s" else: key = base self._dict[key] = val def EndDict(self): "Save dictionary away" self.output.append(self._dict) def OutputStat(self, dict): "Process dictionary of file status info" text = "otherOpen" test = len(text) for key, value in dict.items(): if len(key) > test and key[:test] == text: self.OutputInfo("%s %s" % (key, value), '2') else: self.OutputInfo("%s %s" % (key, value), '1') def HandleError(self, text, severity = None): "Raise an error from P4." # raise P4Client.error(text) if severity in [P4Client.ERROR_EMPTY, P4Client.ERROR_INFO]: self.output.append(text) elif severity == P4Client.ERROR_WARN: self.warnings.append(text) else: self.errors.append(text) class P4: """Class to provide nice interface to P4 (Perforce).""" our_attributes = ["p4client"] # Direct attributes as opposed to ones from client # Translation constants from i18napi.h NOCONV = 0 UTF_8 = 1 ISO8859_1 = 2 UTF_16 = 3 SHIFTJIS = 4 EUCJP = 5 WIN_US_ANSI = 6 WIN_US_OEM = 7 MACOS_ROMAN = 8 ISO8859_15 = 9 ISO8859_5 = 10 KOI8_R = 11 WIN_CP_1251 = 12 UTF_16_LE = 13 UTF_16_BE = 14 UTF_16_LE_BOM = 15 UTF_16_BE_BOM = 16 UTF_16_BOM = 17 UTF_8_BOM = 18 UTF_32 = 19 UTF_32_LE = 20 UTF_32_BE = 21 UTF_32_LE_BOM = 22 UTF_32_BE_BOM = 23 UTF_32_BOM = 24 UTF_8_UNCHECKED = 25 UTF_8_UNCHECKED_BOM = 26 def __init__(self): self.p4client = P4ClientHandler() def connect(self): return self.p4client.connect() def disconnect(self): return self.p4client.disconnect() def api(self, value): "Lock api version in use" self.p4client.set_protocol("api", value) def tagged(self): "Set protocol" self.p4client.tagged() def parse_forms(self): "Set protocol" self.p4client.parse_forms() def set_protocol(self, var, value): "Directly set protocol values" self.p4client.set_protocol(var, value) def translate(self, output, content=-2, fnames=-2, dialog=-2): "Set translation options for international servers - see P4 class constants" self.p4client.translate(output, content, fnames, dialog) def __getattr__(self, name): if name in self.__dict__ or name in self.our_attributes: return self.__dict__[name] else: return getattr(self.p4client, name) def __setattr__(self, name, value): "Allow user to set things like 'port' etc." if not name in self.our_attributes: setattr(self.p4client, name, value) else: self.__dict__[name] = value def run(self, *args): return self.p4client.run(args) 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 line.keys(): print k, "=", line[k] else: print line except: for e in p4.errors: print e
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#13 | 6134 | Robert Cowham |
1.1 - Added raise error on dropped connection - thanks to Ryan Blazecka - Added Solaris option to setup.py (and tidied it a little) |
||
#12 | 6044 | Robert Cowham | Change filetypes | ||
#11 | 6043 | Robert Cowham | Changed license (with Mike's agreement) to make it more easily distributable and also maintainable by Perforce | ||
#10 | 5755 | Robert Cowham |
Added set_protocol() and api() methods. Updated index.html and new Windows dist. |
||
#9 | 5754 | Robert Cowham |
Added new attributes (and tests): GetConfig SetProg SetVersion SetTicketFile |
||
#8 | 5745 | Robert Cowham |
- Added option to read api ver P4.p4_api_ver - Added tests for different P4 API builds Use macro stringification to get P4 API VER. |
||
#7 | 5726 | Robert Cowham |
Handle binary output (e.g. print), together with associated test |
||
#6 | 5057 | Robert Cowham |
- Added P4Error class and catch all errors - shouldn't see P4Client.error any more! - Raise the error when appropriate. - Added translate() function to allow working with Internationalised servers. - Fix problem with diff2 and diff |
||
#5 | 4770 | Robert Cowham |
Changed example to show better usage. Raise AttributeError if appropriate. |
||
#4 | 4766 | Robert Cowham |
Rather better documentation (and license and changelog). Reorganised dirs. Added .zip file and windows binary installer. |
||
#3 | 4759 | Robert Cowham | Added login processing. | ||
#2 | 4756 | Robert Cowham |
Revised substantially. Placeholder for docs. |
||
#1 | 4755 | Robert Cowham |
Rename //guest/robert_cowham/perforce/API/python/P4Client/... To //guest/robert_cowham/perforce/API/python/main/... |
||
//guest/robert_cowham/perforce/API/python/P4Client/p4.py | |||||
#3 | 4618 | Robert Cowham |
Note that the contents of p4.py is now in p4cmd.py as I want to re-use the name p4.py for the more general module case. Started converting package to be more like p4ruby and p4perl in the way it works. |
||
#2 | 4592 | Robert Cowham |
Untabbed. Added setup.py - works for ActiveState Python 2.3! |
||
#1 | 4591 | Robert Cowham | Branch for local mods. | ||
//public/perforce/api/python/P4Client/p4.py | |||||
#1 | 157 | Laura Wingerd |
Publish Mike Meyer's Python api interface. (And delete the stuff I branched into the wrong path.) |
||
//guest/mike_meyer/python/P4Client/p4.py | |||||
#2 | 135 | Mike Meyer |
Changed the P4Clientmodule::OutputInfo to swap the argument order. This allows the same routine to method OutputInfo and OutputText if the level argument is made optional. |
||
#1 | 129 | Mike Meyer | Initial public version. |