/******************************************************************************* Copyright (c) 1997-2004, Perforce Software, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTR IBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL PERFORCE SOFTWARE, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ /******************************************************************************* * Name : php_clientuser.cc * * Author : Tony Smith <tony@perforce.com> or <tony@smee.org> * * Description : PHP bindings for the Perforce API. User interface class * for getting Perforce results into PHP. * ******************************************************************************/ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "php.h" #include "php_ini.h" BEGIN_EXTERN_C() #include "ext/standard/info.h" END_EXTERN_C() #ifdef ZEND_ENGINE_2 #include "zend_object_handlers.h" #endif #include <clientapi.h> #include <spec.h> #include <diff.h> #include "p4result.h" #include "php_clientuser.h" /******************************************************************************* * PhpClientUser - the user interface part. Gets responses from the Perforce * server, and converts the data to PHP form for returning to the caller. ******************************************************************************/ PhpClientUser::PhpClientUser() { debug = 0; input = 0; } void PhpClientUser::Reset() { results.Reset(); lastSpecDef.Clear(); // Leave input alone. } void PhpClientUser::Finished() { // Reset input coz we should be done with it now. if ( debug && input ) fprintf( stderr, "[P4] Cleaning up saved input\n" ); if( input ) { ZVAL_DELREF( input ); input = 0; } } void PhpClientUser::HandleError( Error *e ) { if ( debug ) fprintf( stderr, "[P4] HandleError()\n" ); results.AddError( e ); } void PhpClientUser::OutputText( const_char *data, int length ) { if ( debug ) fprintf( stderr, "[P4] OutputText()\n" ); results.AddOutput( data ); } void PhpClientUser::OutputInfo( char level, const_char *data ) { if ( debug ) fprintf( stderr, "[P4] OutputInfo()\n" ); results.AddOutput( data ); } void PhpClientUser::OutputBinary( const_char *data, int length ) { if ( debug ) fprintf( stderr, "[P4] OutputBinary() %d bytes\n", length ); // // Binary is just stored in a string. Since the char * version of // P4Result::AddOutput() assumes it can strlen() to find the length, // we'll make the String object here. // zval *s; MAKE_STD_ZVAL( s ); ZVAL_STRINGL( s, data, length, 1 ); results.AddOutput( s ); } void PhpClientUser::OutputStat( StrDict *values ) { 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 ) { if ( debug ) fprintf( stderr, "[P4] OutputStat() - parsing form\n" ); // // Save the spec definition for later retrieval by P4ClientApi // 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; } results.AddOutput( DictToHash( specData.Dict(), spec ) ); } else { if ( debug ) fprintf( stderr, "[P4] OutputStat() - converting StrDict to hash\n" ); results.AddOutput( DictToHash( values, NULL ) ); } } /* * Diff support for PHP 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 PhpClientUser::Diff( FileSys *f1, FileSys *f2, int doPage, char *diffFlags, Error *e ) { if ( debug ) fprintf( stderr, "[P4] Diff() - comparing files\n" ); // // Duck binary files. Much the same as ClientUser::Diff, we just // put the output into PHP space rather than stdout. // if( !f1->IsTextual() || !f2->IsTextual() ) { if ( f1->Compare( f2, e ) ) results.AddOutput( "(... files differ ...)" ); 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 ) ) results.AddOutput( b.Text() ); } } delete t; delete f1_bin; delete f2_bin; if ( e->Test() ) HandleError( e ); } /* * convert input from the user into a form digestible to Perforce. This * involves either (a) converting any supplied hash to a Perforce form, or * (b) running to_s on whatever we were given. */ void PhpClientUser::InputData( StrBuf *strbuf, Error *e ) { if( debug ) fprintf( stderr, "[P4] InputData(). Using supplied input\n" ); if( Z_TYPE_P( input ) == IS_ARRAY ) { HashToForm( input, strbuf ); return; } else if( Z_TYPE_P( input ) == IS_STRING ) { strbuf->Set( Z_STRVAL_P( input ) ); return; } // Convert the existing value to a string convert_to_string_ex( &input ); strbuf->Set( Z_STRVAL_P( input ) ); } /* * In a script we don't really want the user to see a prompt, so we * (ab)use the SetInput() function to allow the caller to supply the * answer before the question is asked. */ void PhpClientUser::Prompt( const StrPtr &msg, StrBuf &rsp, int noEcho, Error *e ) { if ( debug ) fprintf( stderr, "[P4] Prompt(): %s\n", msg.Text() ); InputData( &rsp, e ); } /* * Accept input from PHP and convert to a StrBuf for Perforce * purposes. We just save what we're given here because we may not * have the specdef available to parse it with at this time. */ void PhpClientUser::SetInput( zval *i ) { if ( debug ) fprintf( stderr, "[P4] SetInput()\n" ); input = i; // Increment the reference count. ZVAL_ADDREF( input ); } /* * Convert a Perforce StrDict into a PHP hash. Convert multi-level * data (Files0, Files1 etc. ) into (nested) array members of the hash. If * specDef is NULL, then the specDef member will be skipped over, other * wise it will be saved as a wrapped structure in the hash. */ zval * PhpClientUser::DictToHash( StrDict *dict, StrPtr *specDef ) { StrRef var, val; int i; zval * hash = 0; MAKE_STD_ZVAL( hash ); array_init( hash ); for ( i = 0; dict->GetVar( i, var, val ); i++ ) { if ( var == "specdef" && ! specDef ) continue; if ( var == "func" ) continue; InsertItem( hash, &var, &val ); } return hash; } /* * Convert a PHP hash - possibly containing array members into a * formatted StrBuf ready for sending to Perforce. Return 1 or * 0 to indicate success or failure. The form itself is saved in * this->input */ int PhpClientUser::HashToForm( zval * hash, StrBuf * strbuf ) { StrPtr * specDef; if( debug ) printf( "Converting hash to a form...\n" ); specDef = varList->GetVar( "specdef" ); if ( ! specDef ) { php_error(E_NOTICE, "No specdef available. Cannot convert hash " "to a Perforce form" ); return 0; } SpecDataTable specData; Spec s( specDef->Text(), "" ); char * skey; int slen; unsigned long int ikey; zval ** val; int ktype; StrBuf keyBuf; zend_hash_internal_pointer_reset( HASH_OF( hash ) ); while( zend_hash_get_current_data(HASH_OF( hash ), (void **)&val)==SUCCESS) { ktype = zend_hash_get_current_key( HASH_OF( hash ), &skey, &ikey, 0 ); switch( ktype ) { case HASH_KEY_IS_STRING: keyBuf.Set( skey ); break; case HASH_KEY_IS_LONG: php_error( E_WARNING, "Invalid input to Perforce command. " "Did you pass an array instead of a hash?" ); return 0; default: php_error(E_WARNING, "Invalid input to Perforce command. " "Hash key is not a string." ); return 0; } if( Z_TYPE_PP( val ) == IS_ARRAY ) { // Need to flatten the array zval ** val2; int idx = 0; zend_hash_internal_pointer_reset( HASH_OF(*val) ); while( zend_hash_get_current_data( HASH_OF(*val), (void **)&val2) == SUCCESS ) { StrBuf tKey; tKey.Alloc( 32 ); sprintf( tKey.Text(), "%s%d", keyBuf.Text(), idx++ ); if( Z_TYPE_PP( val2 ) == IS_STRING ) { specData.Dict()->SetVar( tKey.Text(), Z_STRVAL_PP( val2 ) ); } else { // Convert the value to a string convert_to_string_ex( val2 ); specData.Dict()->SetVar( tKey.Text(), Z_STRVAL_PP( val2 ) ); } zend_hash_move_forward( HASH_OF( *val ) ); } } else if( Z_TYPE_PP( val ) == IS_STRING ) { specData.Dict()->SetVar( keyBuf.Text(), Z_STRVAL_PP( val ) ); } else { // Convert the value to a string convert_to_string_ex( val ); specData.Dict()->SetVar( keyBuf.Text(), Z_STRVAL_PP( val ) ); } zend_hash_move_forward( HASH_OF( hash ) ); } s.Format( &specData, strbuf ); if( debug ) printf( "----- form -----\n%s\n----- end form -----\n", strbuf->Text() ); return 1; } /* * Split a key into its base name and its index. i.e. for a key "how1,0" * the base name is "how" and they index is "1,0". We work backwards from * the end of the key looking for the first char that is neither a * digit, nor a comma. */ void PhpClientUser::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; } } } /* * Insert an element into the response structure. The element may need to * be inserted into an array nested deeply within the enclosing hash. */ void PhpClientUser::InsertItem( zval * hash, const StrPtr *var, const StrPtr *val ) { zval * ary = 0; zval * tary = 0; zval * key = 0; StrBuf base, index; StrBuf tmp; StrRef comma( "," ); HashTable * ht = Z_ARRVAL_P( hash ); MAKE_STD_ZVAL( key ); SplitKey( var, base, index ); // If there's no index, then we insert into the top level hash // but if the key is already defined then we need to rename the key. This // is probably one of those special keys like otherOpen which can be // both an array element and a scalar. The scalar comes last, so we // just rename it to "otherOpens" to avoid trashing the previous key // value if ( index == "" ) { tmp = *var; if( zend_hash_exists( ht, tmp.Text(), tmp.Length() ) ) tmp.Append( "s" ); add_assoc_string(hash, tmp.Text(), val->Text(), 1 ); return; } // // Get or create the parent array from the hash. // if( zend_hash_find( ht, base.Text(), base.Length(), (void**)&ary ) == FAILURE ) { MAKE_STD_ZVAL( ary ); array_init( ary ); add_assoc_zval(hash, base.Text(), ary ); } // The index may be a simple digit, or it could be a comma separated // list of digits. For each "level" in the index, we need a containing // array. for( const char *c = 0 ; c = index.Contains( comma ); ) { StrBuf level; level.Set( index.Text(), c - index.Text() ); index.Set( c + 1 ); // Found another level so we need to get/create a nested array // under the current entry. We use the level as an index so that // missing entries are left empty deliberately. if( zend_hash_index_find( Z_ARRVAL_P( ary ), level.Atoi(), (void**)&tary ) == FAILURE ) { MAKE_STD_ZVAL( tary ); array_init( tary ); add_index_zval( ary, level.Atoi(), tary ); } ary = tary; } add_next_index_string( ary, val->Text(), 1 ); }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 4627 | Tony Smith |
Rework Jon Parise's PHP interface to work like P4Perl and P4Ruby, mostly. It's pretty close, but lacking method autoloading so the only way to run commands is through the Run() method. This should be considered early beta quality at the moment, Jon will be pulling the bits he likes back into his part of the depot after he's reviewed it. |