/* * Python wrapper for the Perforce ClientApi object. * * Copyright 1999, Mike Meyer. All rights reserved * Modified and Portions Copyright 2005, Robert Cowham. * * License: * See accompanying LICENSE.txt * Original file: * http://public.perforce.com/public/perforce/api/python/P4Client/P4Clientmodule.cc * * Build instructions: * Use Distutils - see accompanying setup.py * * python setup.py install * * If you have an version of Python earlier than 2.2 (where Distutils was included), * it may work (not tested) if you: * * Put this file in the Modules subdirectory of your * Python source tree. Add a line that looks like: * * P4Client P4Clientmodule.cc -I<PERFORCE> -L<PERFORCE> -lclient -lrpc -lsupp * * to the Setup file in that directory. <PERFORCE> is the path to the * directory the where you put the contents of the p4api.tar file. You may * need to add the C++ standard library(ies) at the end of the line as well. * For gcc 2.7.2, that means adding -lgcc to the end of that line. * */ #include "clientapi.h" #include "spec.h" #include "diff.h" #include "i18napi.h" #include "Python.h" #include <vector> #include <string> /* * 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"); Py_DECREF(res); } void OutputError(const char *errBuf) { PyObject *r = PyObject_CallMethod(Py_Client, "OutputError", "(s)", errBuf); Py_XDECREF(r); } void HandleError(Error *err) { StrBuf msg; err->Fmt(&msg); PyObject *r = PyObject_CallMethod(Py_Client, "HandleError", "(si)", msg.Text(), err->GetSeverity()); Py_XDECREF(r); } void OutputInfo(char level, const char *data) { PyObject *r = PyObject_CallMethod(Py_Client, "OutputInfo", "(sc)", data, level); Py_XDECREF(r); } void OutputBinary(const char *data, int length) { PyObject *r = PyObject_CallMethod(Py_Client, "OutputBinary", "(s#)", data, length); Py_XDECREF(r); } void OutputText(const char *data, int length) { PyObject *r = PyObject_CallMethod(Py_Client, "OutputText", "(s#)", data, length); Py_XDECREF(r); } 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 *r = PyObject_CallMethod(Py_Client, "StartDict", "()"); if (!r) return; Py_DECREF(r); for (i = 0; dict->GetVar(i, var, val); i++) { if (var == "specdef" && !specDef) continue; if (var == "func") continue; SplitKey(&var, base, index); r = PyObject_CallMethod(Py_Client, "InsertItem", "(sss)", base.Text(), index.Text(), val.Text()); if (!r) return; Py_DECREF(r); } r = PyObject_CallMethod(Py_Client, "EndDict", "()"); Py_XDECREF(r); } 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)); Py_XDECREF(res); } void ErrorPause(char *errBuf, Error *e) { PyObject *r = PyObject_CallMethod(Py_Client, "ErrorPause", "(s)", errBuf); Py_XDECREF(r); } void Edit(FileSys *f1, Error *e) { PyObject *r = PyObject_CallMethod(Py_Client, "Edit", "(s)", f1->Name()); Py_XDECREF(r); } /* * Diff support for Python API. Since the Diff class only writes its output * to files, we run the requested diff putting the output into a temporary * file. Then we read the file in and add its contents line by line to the * results. */ void Diff(FileSys *f1, FileSys *f2, int doPage, char *diffFlags, Error *e) { // // Duck binary files. Much the same as ClientUser::Diff, we just // put the output into Ruby space rather than stdout. // if(!f1->IsTextual() || !f2->IsTextual()) { if (f1->Compare(f2, e)) { char msg[] = "(... files differ ...)"; OutputText(msg, (int)strlen(msg)); return; } } // Time to diff the two text files. Need to ensure that the // files are in binary mode, so we have to create new FileSys // objects to do this. FileSys *f1_bin = FileSys::Create(FST_BINARY); FileSys *f2_bin = FileSys::Create(FST_BINARY); FileSys *t = FileSys::CreateGlobalTemp(f1->GetType()); f1_bin->Set(f1->Name()); f2_bin->Set(f2->Name()); { // // In its own block to make sure that the diff object is deleted // before we delete the FileSys objects. // #ifndef OS_NEXT :: #endif Diff d; d.SetInput(f1_bin, f2_bin, diffFlags, e); if (!e->Test()) d.SetOutput(t->Name(), e); if (!e->Test()) d.DiffWithFlags(diffFlags); d.CloseOutput(e); // OK, now we have the diff output, read it in and add it to // the output. if (! e->Test()) t->Open(FOM_READ, e); if (! e->Test()) { StrBuf b; while(t->ReadLine(&b, e)) { OutputText(b.Text(), b.Length()); } } } delete t; delete f1_bin; delete f2_bin; if (e->Test()) HandleError(e); } void Merge(FileSys *base, FileSys *leg1, FileSys *leg2, FileSys *result, Error *e) { PyObject *r = PyObject_CallMethod(Py_Client, "Merge", "(ssss)", base->Name(), leg1->Name(), leg2->Name(), result->Name()); Py_XDECREF(r); } void Help(const char *const *help) { PyObject *list = PyList_New(0); if (!list) return; while (*help) { PyObject *s = PyString_FromString(*help); if (!s) { Py_DECREF(list); return; } int fail = PyList_Append(list, s); Py_DECREF(s); if (fail) { Py_DECREF(list); return; } ++help; } PyObject *r = PyObject_CallMethod(Py_Client, "Help", "(O)", list); Py_DECREF(list); Py_XDECREF(r); } 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 */ std::vector<std::string> *mArgv; /* for storing argv things between calls*/ } 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; 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; PyErr_Clear(); 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; error.Fmt(&buffer); PyErr_SetString(P4Error, buffer.Text()); return NULL; } return PyInt_FromLong(i); } static PyObject * dropped(P4ClientObject *me, PyObject *args) { int i; if (!PyArg_ParseTuple(args, ":Dropped")) return NULL; i = me->client->Dropped(); return PyInt_FromLong(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 int _settrans(int *r, PyObject *o) { if (!o) { *r = -2; return 0; } *r = PyInt_AsLong(o); if (*r != -1 || !PyErr_Occurred()) return 0; PyErr_Clear(); const char *s = PyString_AsString(o); if (!s) return -1; *r = (int)CharSetApi::Lookup(s); return 0; } static PyObject * settrans(P4ClientObject *me, PyObject *args) { int output, content=-2, fnames=-2, dialog=-2; PyObject *outputO, *contentO=0, *fnamesO=0, *dialogO=0; if (!PyArg_ParseTuple(args, "O|OOO:SetTrans", &outputO, &contentO, &fnamesO, &dialogO)) return NULL; if (_settrans(&output, outputO)) return 0; if (_settrans(&content, contentO)) return 0; if (_settrans(&fnames, fnamesO)) return 0; if (_settrans(&dialog, dialogO)) return 0; me->client->SetTrans(output, content, fnames, dialog); 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; } me->mArgv->clear(); int count = PySequence_Length(Py_argv); for (i = 0; i < count; i += 1) { PyObject *item = PySequence_GetItem(Py_argv, i); if (!item) return 0; const char *s = PyString_AsString(item); Py_DECREF(item); if (!s) return 0; me->mArgv->push_back(s); } const char **tmp = new const char *[count]; for (i=0; i<count; i++) tmp[i] = (*me->mArgv)[i].c_str(); me->client->SetArgv(count, (char * const *)tmp); delete[] tmp; 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}, {"SetTrans", (PyCFunction) settrans, 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, "version")) { me->client->SetVersion(value); } else if (!strcmp(name, "user")) { me->client->SetUser(value); } else if (!strcmp(name, "os")) { PyErr_SetString(PyExc_AttributeError, "P4Client attribute 'os' is read-only"); return -1; } else { PyErr_SetString(PyExc_AttributeError, name); return -1; } return 0; } /* * The object destroyer. */ static void dealloc(P4ClientObject *gone) { Py_XDECREF(gone->user->Py_Client); delete gone->client; delete gone->mArgv; delete gone->user; 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; if (!PyArg_ParseTuple(args, "O:P4Client", &Py_ui)) return NULL; /* Create the object, and fill it out */ if (!(newp4 = PyObject_NEW(P4ClientObject, &P4ClientType))) return NULL; PythonClientUser *ui = new PythonClientUser; ui->Py_Client = Py_ui; Py_XINCREF(Py_ui); newp4->client = new ClientApi(ui); newp4->mArgv = new std::vector<std::string>; 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" __declspec(dllexport) 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 | |
---|---|---|---|---|---|
#2 | 5752 | Robert Cowham |
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. I have yet to receive confirmation from P4 support on this but my fix fixes a lot of issues. 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. |
||
#1 | 5750 | Robert Cowham | New branch | ||
//guest/robert_cowham/perforce/API/python/main/P4Clientmodule.cc | |||||
#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. |