/* * Python wrapper for the Perforce ClientApi object. * * Copyright 1999, Mike Meyer. All rights reserved * Modified and Portions Copyright 2005, Robert Cowham. * * See license at the end for redistribution permission. * * Build instructions: * Use Distutils - see accompanying setup.py * * python setup.py install * * License: * This file and any derivatives or translations of it may be freely * copied and redistributed so long as: * 1) This license and copyright notice are not changed. * 2) Any fixes or enhancements are reported back to either the * author (mwm@phone.net). * and any of: * a) The source is redistributed with it. * b) The source is compiled unmodified, and instructions for finding * the original source are included. * c) The source is made available by some other means. */ #include "clientapi.h" #include "spec.h" #include "Python.h" /* * The modules variable: * P4Error, the exception that gets raised if there's an error from the * perforce error. */ static PyObject *P4Error; /* * The ClientApi object expects an object to make callbacks to. The * PythonClientUser object maps those to calls to a Python object that * was handed to the P4Client create function - the user PyObject above. */ class PythonClientUser : public ClientUser { public: void InputData(StrBuf *strbuf, Error *e) { PyObject *res = PyObject_CallMethod(Py_Client, "InputData", NULL); if (!res) return; /* Error of some kind - pass it on? */ if (PyString_Check(res)) strbuf->Set(PyString_AS_STRING(res)); else if (PyDict_Check(res)) parse_dict(res, strbuf); else PyErr_SetString(PyExc_TypeError, "InputData must return a string or a hash/dict"); } void OutputError(char *errBuf) { PyObject_CallMethod(Py_Client, "OutputError", "(s)", errBuf); } void HandleError(Error *err) { StrBuf msg; err->Fmt(&msg); PyObject_CallMethod(Py_Client, "HandleError", "(si)", msg.Text(), err->GetSeverity()); } void OutputInfo(char level, char *data) { PyObject_CallMethod(Py_Client, "OutputInfo", "(sc)", data, level); } void OutputBinary(char *data, int length) { PyObject_CallMethod(Py_Client, "OutputBinary", "(s#)", data, length); } void OutputText(char *data, int length) { PyObject_CallMethod(Py_Client, "OutputText", "(s#)", data, length); } void SplitKey(const StrPtr *key, StrBuf &base, StrBuf &index) { int i = 0; base = *key; index = ""; for (i = key->Length(); i; i--) { char prev = (*key)[i-1]; if (!isdigit(prev) && prev != ',') { base.Set(key->Text(), i); index.Set(key->Text() + i); break; } } } // Makes dictionary processing much easier to do it all in python client. void DictToHash(StrDict *dict, StrPtr *specDef) { StrRef var, val; StrBuf base, index; int i; PyObject_CallMethod(Py_Client, "StartDict", "()"); for (i = 0; dict->GetVar(i, var, val); i++) { if (var == "specdef" && !specDef) continue; if (var == "func") continue; SplitKey(&var, base, index); PyObject_CallMethod(Py_Client, "InsertItem", "(sss)", base.Text(), index.Text(), val.Text()); } PyObject_CallMethod(Py_Client, "EndDict", "()"); } void OutputStat(StrDict *values) { StrBuf msg; StrPtr *spec, *data; // If both specdef and data are set, then we need to parse the form // and return the results. If not, then we just convert it as is. spec = values->GetVar("specdef"); data = values->GetVar("data"); if (spec && data) { // Save the spec definition for later use lastSpecDef = spec->Text(); // Parse up the form. Use the ParseNoValid() interface to prevent // errors caused by the use of invalid defaults for select items in // jobspecs. SpecDataTable specData; Spec s(spec->Text(), ""); Error e; s.ParseNoValid(data->Text(), &specData, &e); if (e.Test()) { HandleError(&e); return; } DictToHash(specData.Dict(), spec); } else { DictToHash(values, NULL); } } void Prompt(const StrPtr &msg, StrBuf &buf, int bufsiz, Error *e) { PyObject *res = PyObject_CallMethod(Py_Client, "Prompt", "(s)", msg.Text()); if (res && PyString_Check(res)) buf.Set(PyString_AS_STRING(res)); } void ErrorPause(char *errBuf, Error *e) { PyObject_CallMethod(Py_Client, "ErrorPause", "(s)", errBuf); } void Edit(FileSys *f1, Error *e) { PyObject_CallMethod(Py_Client, "Edit", "(s)", f1->Name()); } void Diff(FileSys *f1, FileSys *f2, int doPage, char *diffFlags, Error *e) { PyObject_CallMethod(Py_Client, "Diff", "(ssis)", f1->Name(), f2->Name(), doPage, diffFlags); } void Merge(FileSys *base, FileSys *leg1, FileSys *leg2, FileSys *result, Error *e) { PyObject_CallMethod(Py_Client, "Merge", "(ssss)", base->Name(), leg1->Name(), leg2->Name(), result->Name()); } void Help(char *const *help) { PyObject *list = PyList_New(0); if (!list) return; while (*help) { PyList_Append(list, PyString_FromString(*help)); help += 1; if (PyErr_Occurred()) { Py_DECREF(list); return; } } PyObject_CallMethod(Py_Client, "Help", "(O)", list); } private: void parse_dict(PyObject *dict, StrBuf *strbuf) { SpecDataTable specData; Spec s(lastSpecDef.Text(), ""); PyObject *key, *value; int pos = 0; while (PyDict_Next(dict, &pos, &key, &value)) { if (PyString_Check(value)) { specData.Dict()->SetVar(PyString_AS_STRING(key), PyString_AS_STRING(value)); } else if (PyList_Check(value)) { StrBuf keyStr; keyStr.Set(PyString_AS_STRING(key)); int i, n; PyObject *item; n = PyList_Size(value); for (i = 0; i < n; i++) { item = PyList_GetItem(value, i); if (!PyString_Check(item)) continue; /* Skip non-strings */ StrBuf tKey; tKey.Alloc(32); sprintf(tKey.Text(), "%s%d", keyStr.Text(), i); specData.Dict()->SetVar(tKey.Text(), PyString_AS_STRING(item)); } } // otherwise ignore! } s.Format(&specData, strbuf); } public: PyObject *Py_Client; private: StrBuf lastSpecDef; StrBuf input; int debug; }; /* * The P4Client Object, which is what this is really here to wrap. */ /* C container for the object */ typedef struct { PyObject_HEAD ClientApi *client; /* The Perforce object we're wrapping */ PythonClientUser *user; /* The object for callbacks */ } P4ClientObject; /* forward decls */ static PyObject * getattr(P4ClientObject *me, char *name); static int setattr(P4ClientObject *me, char *name, PyObject *v); /* * Object Client methods: * Init - establish the connection * Run - run a P4 command * Final - close the connection, return error count. * Dropped - check if the connection is still open. */ static PyObject * init(P4ClientObject *me, PyObject *args) { Error error; if (!PyArg_ParseTuple(args,":Init")) return NULL; me->client->Init(&error); if (error.Test()) { StrBuf *buffer = new StrBuf; error.Fmt(buffer); PyErr_SetString(P4Error, buffer->Text()); return NULL; } Py_INCREF(Py_None); return Py_None; } static PyObject * run(P4ClientObject *me, PyObject *args) { char *command; if (!PyArg_ParseTuple(args, "s:Run", &command)) return NULL; me->client->Run(command); if (PyErr_Occurred()) return NULL; Py_INCREF(Py_None); return Py_None; } static PyObject * final(P4ClientObject *me, PyObject *args) { int i; Error error; if (!PyArg_ParseTuple(args, ":Final")) return NULL; i = me->client->Final(&error); if (error.Test()) { StrBuf *buffer = new StrBuf; error.Fmt(buffer); PyErr_SetString(P4Error, buffer->Text()); return NULL; } return Py_BuildValue("i", i); } static PyObject * dropped(P4ClientObject *me, PyObject *args) { int i; if (!PyArg_ParseTuple(args, ":Dropped")) return NULL; i = me->client->Dropped(); return Py_BuildValue("i", i); } static PyObject * protocol(P4ClientObject *me, PyObject *args) { char *p, *v; if (!PyArg_ParseTuple(args, "ss:SetProtocol", &p, &v)) return NULL; me->client->SetProtocol(p, v); Py_INCREF(Py_None); return Py_None; } static PyObject * setargs(P4ClientObject *me, PyObject *args) { int i; PyObject *Py_argv; if (!PyArg_ParseTuple(args, "O:SetArgv", &Py_argv)) return NULL; if (!PySequence_Check(Py_argv)) { PyErr_SetString(P4Error, "Argument to SetArgv must be a sequence"); return NULL; } int count = PySequence_Length(Py_argv); char **argv = (char **) malloc(count * sizeof(char *)); for (i = 0; i < count; i += 1) { PyObject *item = PySequence_GetItem(Py_argv, i); if (PyString_Check(item)) { argv[i] = PyString_AS_STRING(item); } else { PyErr_SetString(PyExc_TypeError, "P4Client arguments must be strings"); free(argv); return NULL; } } me->client->SetArgv(count, argv); Py_INCREF(Py_None); return Py_None; } /* * Method pointer table for the object methods. */ static struct PyMethodDef methods[] = { {"Init", (PyCFunction) init, METH_VARARGS}, {"Run", (PyCFunction) run, METH_VARARGS}, {"Final", (PyCFunction) final, METH_VARARGS}, {"Dropped", (PyCFunction) dropped, METH_VARARGS}, {"SetProtocol", (PyCFunction) protocol, METH_VARARGS}, {"SetArgv", (PyCFunction) setargs, METH_VARARGS}, {NULL, NULL} }; /* * P4 Client attributes: * Port - host:port to connect to in Init method (set before Init call) * Cwd - current working directory. * Client - client name. * Password - password to use for connection. * User - user name to run as. * Os - Os we are running on, R/O. * */ static PyObject * getattr(P4ClientObject *me, char *name) { if (!strcmp(name, "port")) return PyString_FromString(me->client->GetPort().Text()); if (!strcmp(name, "charset")) return PyString_FromString(me->client->GetCharset().Text()); if (!strcmp(name, "cwd")) return PyString_FromString(me->client->GetCwd().Text()); if (!strcmp(name, "client")) return PyString_FromString(me->client->GetClient().Text()); if (!strcmp(name, "host")) return PyString_FromString(me->client->GetHost().Text()); if (!strcmp(name, "language")) return PyString_FromString(me->client->GetLanguage().Text()); if (!strcmp(name, "password")) return PyString_FromString(me->client->GetPassword().Text()); if (!strcmp(name, "user")) return PyString_FromString(me->client->GetUser().Text()); if (!strcmp(name, "os")) return PyString_FromString(me->client->GetOs().Text()); /* attributes list */ if (!strcmp(name, "__members__")) return Py_BuildValue("[s,s,s,s,s,s,s,s,s]", "charset", "cwd", "client", "host", "language", "port", "password", "user", "os"); return Py_FindMethod(methods, (PyObject *) me, name); } static int setattr(P4ClientObject *me, char *name, PyObject *v) { char *value; if (!PyString_Check(v)) { PyErr_SetString(PyExc_TypeError, "P4Client attribute values must be strings"); return -1; } value = PyString_AS_STRING(v); if (!strcmp(name, "port")) { me->client->SetPort(value); } else if (!strcmp(name, "charset")) { me->client->SetCharset(value); } else if (!strcmp(name, "cwd")) { me->client->SetCwd(value); } else if (!strcmp(name, "client")) { me->client->SetClient(value); } else if (!strcmp(name, "host")) { me->client->SetHost(value); } else if (!strcmp(name, "language")) { me->client->SetLanguage(value); } else if (!strcmp(name, "password")) { me->client->SetPassword(value); } else if (!strcmp(name, "prog")) { me->client->SetProg(value); } else if (!strcmp(name, "user")) { me->client->SetUser(value); } else if (!strcmp(name, "os")) { PyErr_SetString(PyExc_ValueError, "P4Client attribute 'os' is read-only"); return -1; } else { PyErr_SetString(PyExc_NameError, name); return -1; } return 0; } /* * The object destroyer. */ static void dealloc(P4ClientObject *gone) { Py_XDECREF(gone->user->Py_Client); delete gone->client; PyMem_DEL(gone); } /* PyObject object for the P4ClientObject */ static PyTypeObject P4ClientType = { PyObject_HEAD_INIT(&PyType_Type) 0, "P4Client", /* name */ sizeof(P4ClientObject), /* basicsize */ NULL, /* itemsize */ (destructor) dealloc, /* dealloc */ NULL, /* print */ (getattrfunc) getattr, /* getattr */ (setattrfunc) setattr, /* setattr */ NULL, /* compare */ NULL, /* repr */ NULL, /* number methods */ NULL, /* sequence methods */ NULL /* mapping methods */ }; /* * The object creator. */ static PyObject * create(P4ClientObject *dummy, PyObject *args) { P4ClientObject *newp4; PyObject *Py_ui; PythonClientUser *ui = new PythonClientUser; if (!PyArg_ParseTuple(args, "O:P4Client", &Py_ui)) return NULL; /* Create the object, and fill it out */ Py_XINCREF(Py_ui); ui->Py_Client = Py_ui; if (!(newp4 = PyObject_NEW(P4ClientObject, &P4ClientType))) return NULL; newp4->client = new ClientApi(ui); newp4->user = ui; return (PyObject *) newp4; } static struct PyMethodDef functions[] = { {"P4Client", (PyCFunction) create, METH_VARARGS}, {NULL, NULL} }; /* helper function for initialization function, lifted from syslogmodule.c */ static void ins(PyObject *d, char *s, long x) { PyObject *v = PyInt_FromLong(x); if (v) { PyDict_SetItemString(d, s, v); Py_DECREF(v); } } /* * The functions the outside world can see. */ extern "C" void initP4Client() { PyObject *m, *d; m = Py_InitModule4("P4Client", functions, "P4 Client Object", NULL, PYTHON_API_VERSION); d = PyModule_GetDict(m); P4Error = PyErr_NewException("P4Client.error", NULL, NULL); PyDict_SetItemString(d, "error", P4Error); /* Now add the error constants to the module */ ins(d, "ERROR_EMPTY", E_EMPTY); ins(d, "ERROR_INFO", E_INFO); ins(d, "ERROR_WARN", E_WARN); ins(d, "ERROR_FAILED", E_FAILED); ins(d, "ERROR_FATAL", E_FATAL); }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#14 | 6360 | Robert Cowham |
- Remove compiler warning for Python 2.5 vs 2.4 via #ifdef Version 1.2 |
||
#13 | 6044 | Robert Cowham | Change filetypes | ||
#12 | 6043 | Robert Cowham | Changed license (with Mike's agreement) to make it more easily distributable and also maintainable by Perforce | ||
#11 | 5967 | Robert Cowham | - Upgrade to use Perforce API 2007.2 (new structure of api dirs and minor code change) | ||
#10 | 5966 | Robert Cowham |
- Bug fix in spec handling as suggested by Peter Fornwall - Also changed API handling suggested by Peter to allow standard argument parsing - Upgraded version to 1.0 |
||
#9 | 5754 | Robert Cowham |
Added new attributes (and tests): GetConfig SetProg SetVersion SetTicketFile |
||
#8 | 5753 | Robert Cowham |
Merged in changes by Kristj�n V. J�nsson The unicode support in the API seems still not to work entirely (it complains incorrectly that a client spec cannot be used from the server, removing the server from the clientspec fixes that). Anyway, I discovered a number of bugs in P4ClientModule.cc during this process. The most serious seems to be that we need to hold on to the strings we pass to SetArgv(), since it doesn't appear to do so itself. Other bugs had to do mostly with python reference counting and error handling. I also added text handling to the SetTrans() method. I wonder if the "charset" attribute should be supported, since it apparently doesn't do anything in the API. |
||
#7 | 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. |
||
#6 | 5744 | Robert Cowham | Merge in Shawn Hladky's change for 2005.2 API | ||
#5 | 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 |
||
#4 | 4766 | Robert Cowham |
Rather better documentation (and license and changelog). Reorganised dirs. Added .zip file and windows binary installer. |
||
#3 | 4758 | Robert Cowham |
Fix line endings. Make less Windows specific. |
||
#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/P4Clientmodule.cc | |||||
#2 | 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. |
||
#1 | 4591 | Robert Cowham | Branch for local mods. | ||
//public/perforce/api/python/P4Client/P4Clientmodule.cc | |||||
#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/P4Clientmodule.cc | |||||
#3 | 153 | Mike Meyer | Expanded on the build instructions to clarify a few points. | ||
#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. |