From 2387c2a46e15995686d28dccdfd455012072b4cf Mon Sep 17 00:00:00 2001 From: "Matthew J. Francis" Date: Wed, 29 Jul 2015 15:22:54 +0800 Subject: Give PyUNO structs/exceptions their own separate type Change-Id: Ie4c42c623fae1cf39c2e4c643825c9655cd28daa Reviewed-on: https://gerrit.libreoffice.org/17410 Tested-by: Jenkins Reviewed-by: Matthew Francis --- pyuno/Library_pyuno.mk | 1 + pyuno/source/module/pyuno.cxx | 111 ++-------- pyuno/source/module/pyuno_impl.hxx | 9 +- pyuno/source/module/pyuno_module.cxx | 4 +- pyuno/source/module/pyuno_runtime.cxx | 34 ++- pyuno/source/module/pyuno_struct.cxx | 393 ++++++++++++++++++++++++++++++++++ 6 files changed, 438 insertions(+), 114 deletions(-) create mode 100644 pyuno/source/module/pyuno_struct.cxx (limited to 'pyuno') diff --git a/pyuno/Library_pyuno.mk b/pyuno/Library_pyuno.mk index 3bf08ccf3715..49c5b9104e16 100644 --- a/pyuno/Library_pyuno.mk +++ b/pyuno/Library_pyuno.mk @@ -38,6 +38,7 @@ $(eval $(call gb_Library_use_externals,pyuno,\ $(eval $(call gb_Library_add_exception_objects,pyuno,\ pyuno/source/module/pyuno_runtime \ pyuno/source/module/pyuno \ + pyuno/source/module/pyuno_struct \ pyuno/source/module/pyuno_callable \ pyuno/source/module/pyuno_module \ pyuno/source/module/pyuno_type \ diff --git a/pyuno/source/module/pyuno.cxx b/pyuno/source/module/pyuno.cxx index ccab344b5b44..9e9274abc8cb 100644 --- a/pyuno/source/module/pyuno.cxx +++ b/pyuno/source/module/pyuno.cxx @@ -376,26 +376,7 @@ bool lcl_hasInterfaceByName( Any const &object, OUString const & interfaceName ) PyObject *PyUNO_repr( PyObject * self ) { - PyUNO *me = reinterpret_cast(self); - PyObject * ret = 0; - - if( me->members->wrappedObject.getValueType().getTypeClass() - == com::sun::star::uno::TypeClass_EXCEPTION ) - { - Reference< XMaterialHolder > rHolder(me->members->xInvocation,UNO_QUERY); - if( rHolder.is() ) - { - Any a = rHolder->getMaterial(); - Exception e; - a >>= e; - ret = ustring2PyUnicode(e.Message ).getAcquired(); - } - } - else - { - ret = PyUNO_str( self ); - } - return ret; + return PyUNO_str( self ); } Py_hash_t PyUNO_hash( PyObject *self ) @@ -405,20 +386,8 @@ Py_hash_t PyUNO_hash( PyObject *self ) // Py_hash_t is not necessarily the same size as a pointer, but this is not // important for hashing - it just has to return the same value each time - if( me->members->wrappedObject.getValueType().getTypeClass() - == com::sun::star::uno::TypeClass_STRUCT || - me->members->wrappedObject.getValueType().getTypeClass() - == com::sun::star::uno::TypeClass_EXCEPTION ) - { - Reference< XMaterialHolder > xMe( me->members->xInvocation, UNO_QUERY ); - return sal::static_int_cast< Py_hash_t >( reinterpret_cast< sal_IntPtr > ( - *static_cast(xMe->getMaterial().getValue()) ) ); - } - else - { - return sal::static_int_cast< Py_hash_t >( reinterpret_cast< sal_IntPtr > ( - *static_cast(me->members->wrappedObject.getValue()) ) ); - } + return sal::static_int_cast< Py_hash_t >( reinterpret_cast< sal_IntPtr > ( + *static_cast(me->members->wrappedObject.getValue()) ) ); } @@ -501,24 +470,7 @@ PyObject *PyUNO_str( PyObject * self ) OStringBuffer buf; - - if( me->members->wrappedObject.getValueType().getTypeClass() - == com::sun::star::uno::TypeClass_STRUCT || - me->members->wrappedObject.getValueType().getTypeClass() - == com::sun::star::uno::TypeClass_EXCEPTION) - { - Reference< XMaterialHolder > rHolder(me->members->xInvocation,UNO_QUERY); - if( rHolder.is() ) - { - PyThreadDetach antiguard; - Any a = rHolder->getMaterial(); - OUString s = val2str( a.getValue(), a.getValueType().getTypeLibType() ); - buf.append( OUStringToOString(s,RTL_TEXTENCODING_ASCII_US) ); - } - } - else { - // a common UNO object PyThreadDetach antiguard; buf.append( "pyuno object " ); @@ -527,7 +479,7 @@ PyObject *PyUNO_str( PyObject * self ) buf.append( OUStringToOString(s,RTL_TEXTENCODING_ASCII_US) ); } - return PyStr_FromString( buf.getStr()); + return PyStr_FromString( buf.getStr() ); } PyObject* PyUNO_dir (PyObject* self) @@ -1447,18 +1399,15 @@ PyObject* PyUNO_getattr (PyObject* self, char* name) } if (strcmp (name, "__class__") == 0) { - if( me->members->wrappedObject.getValueTypeClass() == - com::sun::star::uno::TypeClass_STRUCT || - me->members->wrappedObject.getValueTypeClass() == - com::sun::star::uno::TypeClass_EXCEPTION ) - { - return getClass( - me->members->wrappedObject.getValueType().getTypeName(), runtime ).getAcquired(); - } Py_INCREF (Py_None); return Py_None; } + PyObject *pRet = PyObject_GenericGetAttr( self, PyUnicode_FromString( name ) ); + if( pRet ) + return pRet; + PyErr_Clear(); + OUString attrName( OUString::createFromAscii( name ) ); //We need to find out if it's a method... if (me->members->xInvocation->hasMethod (attrName)) @@ -1557,7 +1506,6 @@ int PyUNO_setattr (PyObject* self, char* name, PyObject* value) return 1; //as above. } -// ensure object identity and struct equality static PyObject* PyUNO_cmp( PyObject *self, PyObject *that, int op ) { PyObject *result; @@ -1586,26 +1534,11 @@ static PyObject* PyUNO_cmp( PyObject *self, PyObject *that, int op ) if( tcMe == tcOther ) { - if( tcMe == com::sun::star::uno::TypeClass_STRUCT || - tcMe == com::sun::star::uno::TypeClass_EXCEPTION ) + if( me->members->wrappedObject == other->members->wrappedObject ) { - Reference< XMaterialHolder > xMe( me->members->xInvocation,UNO_QUERY); - Reference< XMaterialHolder > xOther( other->members->xInvocation,UNO_QUERY ); - if( xMe->getMaterial() == xOther->getMaterial() ) - { - result = (op == Py_EQ ? Py_True : Py_False); - Py_INCREF(result); - return result; - } - } - else if( tcMe == com::sun::star::uno::TypeClass_INTERFACE ) - { - if( me->members->wrappedObject == other->members->wrappedObject ) - { - result = (op == Py_EQ ? Py_True : Py_False); - Py_INCREF(result); - return result; - } + result = (op == Py_EQ ? Py_True : Py_False); + Py_INCREF(result); + return result; } } } @@ -1768,8 +1701,7 @@ PyRef getPyUnoClass() PyRef PyUNO_new ( const Any &targetInterface, - const Reference &ssf, - const bool bCheckExisting ) + const Reference &ssf ) { Reference xInvocation; @@ -1780,16 +1712,13 @@ PyRef PyUNO_new ( if( !xInvocation.is() ) throw RuntimeException("XInvocation2 not implemented, cannot interact with object"); - if (bCheckExisting) + Reference xUnoTunnel ( + xInvocation->getIntrospection()->queryAdapter(cppu::UnoType::get()), UNO_QUERY ); + if( xUnoTunnel.is() ) { - Reference xUnoTunnel ( - xInvocation->getIntrospection()->queryAdapter(cppu::UnoType::get()), UNO_QUERY ); - if( xUnoTunnel.is() ) - { - sal_Int64 that = xUnoTunnel->getSomething( ::pyuno::Adapter::getUnoTunnelImplementationId() ); - if( that ) - return PyRef( reinterpret_cast(that)->getWrappedObject() ); - } + sal_Int64 that = xUnoTunnel->getSomething( ::pyuno::Adapter::getUnoTunnelImplementationId() ); + if( that ) + return PyRef( reinterpret_cast(that)->getWrappedObject() ); } } if( !Py_IsInitialized() ) diff --git a/pyuno/source/module/pyuno_impl.hxx b/pyuno/source/module/pyuno_impl.hxx index 669386f090e0..727e4884e08d 100644 --- a/pyuno/source/module/pyuno_impl.hxx +++ b/pyuno/source/module/pyuno_impl.hxx @@ -204,11 +204,15 @@ typedef std::unordered_map typedef std::unordered_set< PyRef , PyRef::Hash , std::equal_to > ClassSet; int PyUNO_initType(); +int PyUNOStruct_initType(); PyRef PyUNO_new ( const com::sun::star::uno::Any & targetInterface, - const com::sun::star::uno::Reference & ssf, - const bool bCheckExisting ); + const com::sun::star::uno::Reference & ssf ); + +PyRef PyUNOStruct_new ( + const com::sun::star::uno::Any &targetInterface, + const com::sun::star::uno::Reference &ssf ); typedef struct { @@ -283,6 +287,7 @@ PyRef getBoolClass( const Runtime &); PyRef getCharClass( const Runtime &); PyRef getByteSequenceClass( const Runtime & ); PyRef getPyUnoClass(); +PyRef getPyUnoStructClass(); PyRef getClass( const OUString & name , const Runtime & runtime ); PyRef getAnyClass( const Runtime &); PyObject *PyUNO_invoke( PyObject *object, const char *name , PyObject *args ); diff --git a/pyuno/source/module/pyuno_module.cxx b/pyuno/source/module/pyuno_module.cxx index 7d282c2f27c7..01d5a62bacd2 100644 --- a/pyuno/source/module/pyuno_module.cxx +++ b/pyuno/source/module/pyuno_module.cxx @@ -406,7 +406,7 @@ static PyObject *createUnoStructHelper( if (idl_class.is ()) { idl_class->createObject (IdlStruct); - PyRef returnCandidate( PyUNO_new( IdlStruct, c->xInvocation, false ) ); + PyRef returnCandidate( PyUNOStruct_new( IdlStruct, c->xInvocation ) ); PyUNO *me = reinterpret_cast( returnCandidate.get() ); TypeDescription desc( typeName ); OSL_ASSERT( desc.is() ); // could already instantiate an XInvocation2 ! @@ -862,6 +862,7 @@ extern "C" PyObject* PyInit_pyuno() { PyUNO_initType(); + PyUNOStruct_initType(); // noop when called already, otherwise needed to allow multiple threads PyEval_InitThreads(); static struct PyModuleDef moduledef = @@ -882,6 +883,7 @@ PyObject* PyInit_pyuno() void initpyuno() { PyUNO_initType(); + PyUNOStruct_initType(); PyEval_InitThreads(); Py_InitModule ("pyuno", PyUNOModule_methods); } diff --git a/pyuno/source/module/pyuno_runtime.cxx b/pyuno/source/module/pyuno_runtime.cxx index f14b84b4baa7..0219742dcef0 100644 --- a/pyuno/source/module/pyuno_runtime.cxx +++ b/pyuno/source/module/pyuno_runtime.cxx @@ -481,7 +481,7 @@ PyRef Runtime::any2PyObject (const Any &a ) const case typelib_TypeClass_STRUCT: { PyRef excClass = getClass( a.getValueType().getTypeName(), *this ); - PyRef value = PyUNO_new( a, getImpl()->cargo->xInvocation, false ); + PyRef value = PyUNOStruct_new( a, getImpl()->cargo->xInvocation ); PyRef argsTuple( PyTuple_New( 1 ) , SAL_NO_ACQUIRE, NOT_NULL ); PyTuple_SetItem( argsTuple.get() , 0 , value.getAcquired() ); PyRef ret( PyObject_CallObject( excClass.get() , argsTuple.get() ), SAL_NO_ACQUIRE ); @@ -556,7 +556,7 @@ PyRef Runtime::any2PyObject (const Any &a ) const if (!tmp_interface.is ()) return Py_None; - return PyUNO_new (a, getImpl()->cargo->xInvocation, true); + return PyUNO_new( a, getImpl()->cargo->xInvocation ); } default: { @@ -802,27 +802,21 @@ Any Runtime::pyObject2Any ( const PyRef & source, enum ConversionMode mode ) con } else if( PyObject_IsInstance( o, getPyUnoClass().get() ) ) { - PyUNO* o_pi; - o_pi = reinterpret_cast(o); - if (o_pi->members->wrappedObject.getValueTypeClass () == - com::sun::star::uno::TypeClass_STRUCT || - o_pi->members->wrappedObject.getValueTypeClass () == - com::sun::star::uno::TypeClass_EXCEPTION) - { - Reference my_mh (o_pi->members->xInvocation, UNO_QUERY); + PyUNO* o_pi = reinterpret_cast(o); + a = o_pi->members->wrappedObject; + } + else if( PyObject_IsInstance( o, getPyUnoStructClass().get() ) ) + { + PyUNO* o_pi = reinterpret_cast(o); + Reference my_mh (o_pi->members->xInvocation, UNO_QUERY); - if (!my_mh.is ()) - { - throw RuntimeException( - "struct wrapper does not support XMaterialHolder" ); - } - else - a = my_mh->getMaterial (); - } - else + if (!my_mh.is()) { - a = o_pi->members->wrappedObject; + throw RuntimeException( + "struct wrapper does not support XMaterialHolder" ); } + else + a = my_mh->getMaterial(); } else if( PyObject_IsInstance( o, getCharClass( runtime ).get() ) ) { diff --git a/pyuno/source/module/pyuno_struct.cxx b/pyuno/source/module/pyuno_struct.cxx new file mode 100644 index 000000000000..b3413861cb55 --- /dev/null +++ b/pyuno/source/module/pyuno_struct.cxx @@ -0,0 +1,393 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include + +#include "pyuno_impl.hxx" + +#include + +#include + +#include + +#include + +using com::sun::star::uno::Sequence; +using com::sun::star::uno::Reference; +using com::sun::star::uno::XInterface; +using com::sun::star::uno::Any; +using com::sun::star::uno::makeAny; +using com::sun::star::uno::UNO_QUERY; +using com::sun::star::uno::Type; +using com::sun::star::uno::TypeClass; +using com::sun::star::uno::RuntimeException; +using com::sun::star::uno::Exception; +using com::sun::star::lang::XSingleServiceFactory; +using com::sun::star::lang::XUnoTunnel; +using com::sun::star::script::XInvocation2; +using com::sun::star::beans::XMaterialHolder; + +namespace pyuno +{ + +void PyUNOStruct_del( PyObject* self ) +{ + PyUNO *me = reinterpret_cast( self ); + { + PyThreadDetach antiguard; + delete me->members; + } + PyObject_Del( self ); +} + +PyObject *PyUNOStruct_str( PyObject *self ) +{ + PyUNO *me = reinterpret_cast( self ); + OStringBuffer buf; + + Reference rHolder( me->members->xInvocation,UNO_QUERY ); + if( rHolder.is() ) + { + PyThreadDetach antiguard; + Any a = rHolder->getMaterial(); + OUString s = val2str( a.getValue(), a.getValueType().getTypeLibType() ); + buf.append( OUStringToOString( s, RTL_TEXTENCODING_ASCII_US ) ); + } + + return PyStr_FromString( buf.getStr()); +} + +PyObject *PyUNOStruct_repr( PyObject *self ) +{ + PyUNO *me = reinterpret_cast( self ); + PyObject *ret = 0; + + if( me->members->wrappedObject.getValueType().getTypeClass() + == com::sun::star::uno::TypeClass_EXCEPTION ) + { + Reference< XMaterialHolder > rHolder(me->members->xInvocation,UNO_QUERY); + if( rHolder.is() ) + { + Any a = rHolder->getMaterial(); + Exception e; + a >>= e; + ret = ustring2PyUnicode(e.Message ).getAcquired(); + } + } + else + { + ret = PyUNOStruct_str( self ); + } + + return ret; +} + +PyObject* PyUNOStruct_dir( PyObject *self ) +{ + PyUNO *me = reinterpret_cast( self ); + + PyObject* member_list = NULL; + + try + { + member_list = PyList_New( 0 ); + for( auto aMember : me->members->xInvocation->getMemberNames() ) + { + // setitem steals a reference + PyList_Append( member_list, ustring2PyString( aMember ).getAcquired() ); + } + } + catch( const RuntimeException &e ) + { + raisePyExceptionWithAny( makeAny(e) ); + } + + return member_list; +} + +PyObject* PyUNOStruct_getattr( PyObject* self, char* name ) +{ + PyUNO *me = reinterpret_cast( self ); + + try + { + Runtime runtime; + + me = reinterpret_cast(self); + if (strcmp (name, "__dict__") == 0) + { + Py_INCREF (Py_TYPE(me)->tp_dict); + return Py_TYPE(me)->tp_dict; + } + if( strcmp( name, "__class__" ) == 0 ) + { + return getClass( + me->members->wrappedObject.getValueType().getTypeName(), runtime ).getAcquired(); + } + + PyObject *pRet = PyObject_GenericGetAttr( self, PyUnicode_FromString( name ) ); + if( pRet ) + return pRet; + PyErr_Clear(); + + OUString attrName( OUString::createFromAscii( name ) ); + if( me->members->xInvocation->hasProperty( attrName ) ) + { + //Return the value of the property + Any anyRet; + { + PyThreadDetach antiguard; + anyRet = me->members->xInvocation->getValue( attrName ); + } + PyRef ret = runtime.any2PyObject( anyRet ); + Py_XINCREF( ret.get() ); + return ret.get(); + } + + //or else... + PyErr_SetString (PyExc_AttributeError, name); + } + catch( const com::sun::star::reflection::InvocationTargetException & e ) + { + raisePyExceptionWithAny( e.TargetException ); + } + catch( const com::sun::star::beans::UnknownPropertyException & e ) + { + raisePyExceptionWithAny( makeAny(e) ); + } + catch( const com::sun::star::lang::IllegalArgumentException &e ) + { + raisePyExceptionWithAny( makeAny(e) ); + } + catch( const com::sun::star::script::CannotConvertException &e ) + { + raisePyExceptionWithAny( makeAny(e) ); + } + catch( const RuntimeException &e ) + { + raisePyExceptionWithAny( makeAny(e) ); + } + + return NULL; +} + +int PyUNOStruct_setattr (PyObject* self, char* name, PyObject* value) +{ + PyUNO* me; + + me = reinterpret_cast(self); + try + { + Runtime runtime; + Any val= runtime.pyObject2Any(value, ACCEPT_UNO_ANY); + + OUString attrName( OUString::createFromAscii( name ) ); + { + PyThreadDetach antiguard; + if (me->members->xInvocation->hasProperty (attrName)) + { + me->members->xInvocation->setValue (attrName, val); + return 0; //Keep with Python's boolean system + } + } + } + catch( const com::sun::star::reflection::InvocationTargetException & e ) + { + raisePyExceptionWithAny( e.TargetException ); + return 1; + } + catch( const com::sun::star::beans::UnknownPropertyException & e ) + { + raisePyExceptionWithAny( makeAny(e) ); + return 1; + } + catch( const com::sun::star::script::CannotConvertException &e ) + { + raisePyExceptionWithAny( makeAny(e) ); + return 1; + } + catch( const RuntimeException & e ) + { + raisePyExceptionWithAny( makeAny( e ) ); + return 1; + } + PyErr_SetString (PyExc_AttributeError, name); + return 1; //as above. +} + + +static PyObject* PyUNOStruct_cmp( PyObject *self, PyObject *that, int op ) +{ + PyObject *result; + + if(op != Py_EQ && op != Py_NE) + { + PyErr_SetString( PyExc_TypeError, "only '==' and '!=' comparisons are defined" ); + return 0; + } + if( self == that ) + { + result = (op == Py_EQ ? Py_True : Py_False); + Py_INCREF( result ); + return result; + } + try + { + Runtime runtime; + if( PyObject_IsInstance( that, getPyUnoStructClass().get() ) ) + { + + PyUNO *me = reinterpret_cast< PyUNO * > ( self ); + PyUNO *other = reinterpret_cast< PyUNO * > ( that ); + com::sun::star::uno::TypeClass tcMe = me->members->wrappedObject.getValueTypeClass(); + com::sun::star::uno::TypeClass tcOther = other->members->wrappedObject.getValueTypeClass(); + + if( tcMe == tcOther ) + { + if( tcMe == com::sun::star::uno::TypeClass_STRUCT || + tcMe == com::sun::star::uno::TypeClass_EXCEPTION ) + { + Reference< XMaterialHolder > xMe( me->members->xInvocation,UNO_QUERY ); + Reference< XMaterialHolder > xOther( other->members->xInvocation,UNO_QUERY ); + if( xMe->getMaterial() == xOther->getMaterial() ) + { + result = (op == Py_EQ ? Py_True : Py_False); + Py_INCREF( result ); + return result; + } + } + } + } + } + catch( const com::sun::star::uno::RuntimeException & e) + { + raisePyExceptionWithAny( makeAny( e ) ); + } + + result = (op == Py_EQ ? Py_False : Py_True); + Py_INCREF(result); + return result; +} + +static PyMethodDef PyUNOStructMethods[] = +{ + {"__dir__", reinterpret_cast(PyUNOStruct_dir), METH_NOARGS, NULL}, + {NULL, NULL, 0, NULL} +}; + +static PyTypeObject PyUNOStructType = +{ + PyVarObject_HEAD_INIT( &PyType_Type, 0 ) + "pyuno.struct", + sizeof (PyUNO), + 0, + PyUNOStruct_del, + nullptr, + PyUNOStruct_getattr, + PyUNOStruct_setattr, + /* this type does not exist in Python 3: (cmpfunc) */ 0, + PyUNOStruct_repr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + PyUNOStruct_str, + nullptr, + nullptr, + nullptr, + Py_TPFLAGS_HAVE_RICHCOMPARE, + nullptr, + nullptr, + nullptr, + PyUNOStruct_cmp, + 0, + nullptr, + nullptr, + PyUNOStructMethods, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + 0, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr +#if PY_VERSION_HEX >= 0x02060000 + , 0 +#endif +#if PY_VERSION_HEX >= 0x03040000 + , 0 +#endif +}; + +int PyUNOStruct_initType() +{ + return PyType_Ready( &PyUNOStructType ); +} + +PyRef getPyUnoStructClass() +{ + return PyRef( reinterpret_cast< PyObject * > ( &PyUNOStructType ) ); +} + +PyRef PyUNOStruct_new ( + const Any &targetInterface, + const Reference &ssf ) +{ + Reference xInvocation; + + { + PyThreadDetach antiguard; + xInvocation.set( + ssf->createInstanceWithArguments( Sequence( &targetInterface, 1 ) ), UNO_QUERY ); + OSL_ASSERT( xInvocation.is() ); + if( !xInvocation.is() ) + throw RuntimeException("XInvocation2 not implemented, cannot interact with object"); + } + if( !Py_IsInitialized() ) + throw RuntimeException(); + + PyUNO* self = PyObject_New (PyUNO, &PyUNOStructType); + if (self == NULL) + return PyRef(); // == error + self->members = new PyUNOInternals(); + self->members->xInvocation = xInvocation; + self->members->wrappedObject = targetInterface; + return PyRef( reinterpret_cast(self), SAL_NO_ACQUIRE ); + +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ -- cgit