/* * 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" /* * 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; if (var == "specFormatted") 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) { // Save the spec definition for later use lastSpecDef = spec->Text(); } if (data) { // 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()); } /* * 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, 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_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 * settrans(P4ClientObject *me, PyObject *args) { int output, content, fnames, dialog; if (!PyArg_ParseTuple(args, "iiii:SetTrans", &output, &content, &fnames, &dialog)) return NULL; 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; } 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}, {"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, "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 | |
---|---|---|---|---|---|
#2 | 5579 | Shawn Hladky | fixed so forms work w/ 2005.2 API | ||
#1 | 5578 | Shawn Hladky | branching P4Python | ||
//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. |