/******************************************************************************* 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 : specmgr.cc * * Author : Tony Smith <tony@perforce.com> or <tony@smee.org> * * Description : Ruby bindings for the Perforce API. Class for handling * Perforce specs. This class provides other classes with * generic support for parsing and formatting Perforce * specs. * ******************************************************************************/ #include <ctype.h> #include <ruby.h> #include "undefdups.h" #include <clientapi.h> #include <strops.h> #include <spec.h> #include "extconf.h" #include "gc_hack.h" #include "p4rubydebug.h" #include "specmgr.h" // // Convert a Perforce StrDict into a Ruby hash. Convert multi-level // data (Files0, Files1 etc. ) into (nested) array members of the hash. // VALUE SpecMgr::DictToHash( StrDict *dict, VALUE hash ) { StrRef var, val; int i; if( hash == Qnil ) hash = rb_hash_new(); for ( i = 0; dict->GetVar( i, var, val ); i++ ) { if ( var == "specdef" || var == "func" || var == "specFormatted" ) continue; InsertItem( hash, &var, &val ); } return hash; } // // Convert a Perforce StrDict into a P4::Spec object. This is essentially // a hash with some additional restrictions. // VALUE SpecMgr::DictToSpec( StrDict *dict, StrPtr *specDef ) { VALUE spec = NewSpec( specDef ); return DictToHash( dict, spec ); } // // Convert a Ruby hash - possibly containing array members into a // formatted StrBuf ready for sending to Perforce. Return Qtrue or // Qfalse to indicate success or failure. The form itself is saved in // this->input // int SpecMgr::HashToText( VALUE hash, StrBuf *strbuf, StrPtr *specDef, Error *e ) { if ( ! specDef ) { rb_warn( "No specdef available. Cannot convert hash to a " "Perforce form" ); return 0; } SpecDataTable specData; #if P4APIVER_ID >= 513538 Spec s( specDef->Text(), "", e ); if( e->Test() ) return Qfalse; #else Spec s( specDef->Text(), "" ); #endif ID idKeys = rb_intern( "keys" ); ID idLength = rb_intern( "length" ); ID idToS = rb_intern( "to_s" ); VALUE keys = rb_funcall( hash, idKeys, 0 ); int keyCount = NUM2INT( rb_funcall( keys, idLength, 0 ) ); for ( int idx = 0; idx < keyCount; idx++ ) { VALUE key; VALUE val; char * tVal = 0; StrBuf keyStr; key = rb_ary_entry( keys, idx ); if ( key == Qnil ) break; keyStr.Set( STR2CSTR( rb_funcall( key, idToS, 0 ) ) ); val = rb_hash_aref( hash, key ); if ( rb_obj_is_kind_of( val, rb_cArray ) ) { // Need to flatten the array VALUE subVal; for( int idx2 = 0; (subVal = rb_ary_entry( val, idx2 ) ) ; idx2++ ) { if ( subVal == Qnil ) break; StrBuf tKey; tKey.Alloc( 32 ); sprintf( tKey.Text(), "%s%d", keyStr.Text(), idx2 ); tVal = STR2CSTR( rb_funcall( subVal, idToS, 0 ) ); specData.Dict()->SetVar( tKey.Text(), tVal ); if( P4RDB_DATA ) fprintf( stderr, "... %s -> %s\n", tKey.Text(), tVal ); } } else { tVal = STR2CSTR( rb_funcall( val, idToS, 0 ) ); specData.Dict()->SetVar( keyStr.Text(), tVal ); if( P4RDB_DATA ) fprintf( stderr, "... %s -> %s\n", keyStr.Text(), tVal ); } } s.Format( &specData, strbuf ); return 1; } // // This method returns a hash describing the valid fields in the spec. To // make it easy on our users, we map the lowercase name to the name defined // in the spec. Thus, the users can always user lowercase, and if the field // should be in mixed case, it will be. See P4::Spec::method_missing // VALUE SpecMgr::SpecFields( StrPtr *specDef ) { // // There's no trivial way to do this using the API (and get it right), so // for now, we parse the string manually. We're ignoring the type of // the field, and any constraints it may be under; what we're interested // in is solely the field name // VALUE hash = rb_hash_new(); const char *b, *e; const char *sep = ";"; const char *fsep = ";;"; const char *seek = sep; for( e = b = specDef->Text(); e && b ; ) { e = strstr( b, seek ); if( e && seek == sep ) { StrBuf k; k.Set( b, e - b ); StrOps::Lower( k ); rb_hash_aset(hash, rb_str_new2( k.Text() ), rb_str_new( b, e - b )); b = ++e; seek = fsep; } else if( e ) { b = e += 2; seek = sep; } } return hash; } // // 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 SpecMgr::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 SpecMgr::InsertItem( VALUE hash, const StrPtr *var, const StrPtr *val ) { VALUE ary = 0; VALUE tary = 0; VALUE key; ID idLength = rb_intern( "length" ); StrBuf base, index; StrRef comma( "," ); 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 == "" ) { ID idHasKey = rb_intern( "has_key?"); ID idPlus = rb_intern( "+" ); key = rb_str_new2( var->Text() ); if ( rb_funcall( hash, idHasKey, 1, key ) == Qtrue ) key = rb_funcall( key, idPlus, 1, rb_str_new2( "s" ) ); if( P4RDB_DATA ) fprintf( stderr, "... %s -> %s\n", STR2CSTR( key ), val->Text() ); rb_hash_aset( hash, key, rb_str_new2( val->Text() ) ); return; } // // Get or create the parent array from the hash. // key = rb_str_new2( base.Text() ); ary = rb_hash_aref( hash, key ); if ( Qnil == ary ) { ary = rb_ary_new(); rb_hash_aset( hash, key, ary ); } else if( rb_obj_is_kind_of( ary, rb_cArray ) != Qtrue ) { // // There's an index in our var name, but the name is already defined // and the value it contains is not an array. This means we've got a // name collision. This can happen in 'p4 diff2' for example, when // one file gets 'depotFile' and the other gets 'depotFile2'. In // these cases it makes sense to keep the structure flat so we // just use the raw variable name. // if( P4RDB_DATA ) fprintf( stderr, "... %s -> %s\n", var->Text(), val->Text() ); rb_hash_aset( hash, rb_str_new2( var->Text() ) , rb_str_new2( val->Text() ) ); return; } // 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. if( P4RDB_DATA ) fprintf( stderr, "... %s -> [", base.Text() ); 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. tary = rb_ary_entry( ary, level.Atoi() ); if ( ! RTEST( tary ) ) { tary = rb_ary_new(); rb_ary_store( ary, level.Atoi(), tary ); } if( P4RDB_DATA ) fprintf( stderr, "%s][", level.Text() ); ary = tary; } int arylen = NUM2INT( rb_funcall( ary, idLength, 0 ) ); if( P4RDB_DATA ) fprintf( stderr, "%d] = %s\n", arylen, val->Text() ); rb_ary_push( ary, rb_str_new2( val->Text() ) ); } // // Create a new P4::Spec object and return it. // VALUE SpecMgr::NewSpec( StrPtr *specDef ) { ID idNew = rb_intern( "new" ); ID idP4 = rb_intern( "P4" ); ID idP4Spec = rb_intern( "Spec" ); VALUE cP4 = rb_const_get_at( rb_cObject, idP4 ); VALUE cP4Spec = rb_const_get_at( cP4, idP4Spec ); VALUE fields = SpecFields( specDef ); return rb_funcall( cP4Spec, idNew, 1, fields ); }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#5 | 5792 | Tony Smith | Update P4Ruby to support 2006.2 beta API. | ||
#4 | 5693 | Tony Smith |
Update p4conf.rb to define const_char on all platforms if building against a 2006.1 or later API. Also squelched some compiler noise. No functional change |
||
#3 | 5258 | Tony Smith |
Adapt P4Ruby for 2005.2 API changes. The 2005.2 API supplies forms ready-parsed in tagged mode and in general P4Ruby worked with it. The only issue was that P4Ruby wasn't caching the specdefs properly so conversions in the reverse direction were broken. This change ensures that we cache the specdef if we have it regardless of the API level. Squelched on a compiler warning too while I was there. |
||
#2 | 5222 | Tony Smith |
Improve debug output in P4Ruby: p4.debug = 1 * Show commands being executed p4.debug = 2 * Show function calls p4.debug = 3 * Show data p4.debug = 4 * Show ruby garbage collection calls. Debug levels are cumulative as you'd expect. |
||
#1 | 4680 | Tony Smith |
Make P4Ruby return new P4::Spec objects instead of plain old hashes when parse_forms mode is in use. A P4::Spec object is derived from Hash so should be backwards compatible with previous code. P4::Spec provides limited fieldname validation on forms and accessor methods for quick and easy access to the fields in the form. The accessor methods are all prefixed with '_' to avoid colliding with methods from the Hash parent class. This is a little ugly, but deriving from hash is a big win, so it's worth it. This change also fixes a minor bug found along the way. Spec parsing and formatting wouldn't work with labels, branches, depots and groups unless you'd previously run a P4::fetch_label( <label> ), P4::fetch_branch( <branch> ) etc. etc. This is because the spec parsing code internally runs one of these commands in order to grab the specdef from the server but it wasn't providing a spec name. i.e. it was using 'p4 client -o' and assuming that this would work for other types of spec too. It does, but not for all spec types. So, now the spec parsing code will use a bogus name for the spec types that require it. |