diff --git a/CMakeModules/TokenList2DsnLexer.cmake b/CMakeModules/TokenList2DsnLexer.cmake index 4eefa003e8..c1d2884468 100644 --- a/CMakeModules/TokenList2DsnLexer.cmake +++ b/CMakeModules/TokenList2DsnLexer.cmake @@ -220,6 +220,13 @@ extern const unsigned ${result}_keyword_count; using namespace DSN; // enum ${enum} is in this namespace + +/** + * Classs ${RESULT}_LEXER + * is an automatically generated class using the TokenList2DnsLexer.cmake + * technology, based on keywords provided by file: + * ${inputFile} + */ class ${RESULT}_LEXER : public DSNLEXER { public: diff --git a/new/CMakeLists.txt b/new/CMakeLists.txt index b6eedc64e4..518a45f54d 100644 --- a/new/CMakeLists.txt +++ b/new/CMakeLists.txt @@ -77,6 +77,11 @@ target_link_libraries( test_sch_lib_table ${wxWidgets_LIBRARIES} ) add_executable( test_sch_part sch_part.cpp ) target_link_libraries( test_sch_part ${wxWidgets_LIBRARIES} ) +add_executable( test_lpid + sch_lpid.cpp + ) +target_link_libraries( test_lpid ${wxWidgets_LIBRARIES} ) + make_lexer( ${CMAKE_CURRENT_SOURCE_DIR}/sch_lib_table.keywords @@ -84,4 +89,3 @@ make_lexer( ${CMAKE_CURRENT_SOURCE_DIR}/sch_lib_table_keywords.cpp ELT_T ) - diff --git a/new/design.h b/new/design.h index 9ccfc28ced..0aadf2ec57 100644 --- a/new/design.h +++ b/new/design.h @@ -443,4 +443,6 @@ public: } // namespace SCH +/// @todo remove endsWithRev() in favor of EndsWithRev(), find home for it. + // EOF diff --git a/new/sch_dir_lib_source.cpp b/new/sch_dir_lib_source.cpp index 4809c2bc77..70cc3040da 100644 --- a/new/sch_dir_lib_source.cpp +++ b/new/sch_dir_lib_source.cpp @@ -140,6 +140,13 @@ static const char* strrstr( const char* haystack, const char* needle ) return ret; } +#if 1 // @todo switch over to EndsWithRev() global + +static inline bool isDigit( char c ) +{ + return c >= '0' && c <= '9'; +} + static inline bool isDigit( char c ) { @@ -185,6 +192,8 @@ static inline const char* endsWithRev( const STRING& aPartName, char separator ) return endsWithRev( aPartName.c_str(), aPartName.c_str()+aPartName.size(), separator ); } +#endif + // see struct BY_REV bool BY_REV::operator() ( const STRING& s1, const STRING& s2 ) const @@ -587,9 +596,9 @@ void DIR_LIB_SOURCE::cacheOneDir( const STRING& aCategory ) throw( IO_ERROR ) } -#if (1 || defined( TEST_DIR_LIB_SOURCE )) && defined(DEBUG) +#if 1 && defined(DEBUG) -int main( int argc, char** argv ) +void DIR_LIB_SOURCE::Test( int argc, char** argv ) { STRINGS partnames; STRINGS sweets; @@ -601,7 +610,8 @@ int main( int argc, char** argv ) // DIR_LIB_SOURCE uut( argv[1] ? argv[1] : "", "" ); DIR_LIB_SOURCE uut( argv[1] ? argv[1] : "", "useVersioning" ); - // initially, only the NAME_CACHE sweets and STRING categories are loaded: + // show the cached content, only the directory information is cached, + // parts are cached in class LIB, not down here. uut.Show(); uut.GetCategoricalPartNames( &partnames, "lions" ); @@ -649,7 +659,12 @@ int main( int argc, char** argv ) { printf( "exception: %s\n", (const char*) wxConvertWX2MB( ioe.errorText ) ); } +} + +int main( int argc, char** argv ) +{ + DIR_LIB_SOURCE::Test( argc, argv ); return 0; } diff --git a/new/sch_dir_lib_source.h b/new/sch_dir_lib_source.h index e12f72113a..1e6d09f38f 100644 --- a/new/sch_dir_lib_source.h +++ b/new/sch_dir_lib_source.h @@ -132,8 +132,7 @@ class DIR_LIB_SOURCE : public LIB_SOURCE */ STRING makeFileName( const STRING& aPartName ); -//protected: -public: +protected: /** * Constructor DIR_LIB_SOURCE( const STRING& aDirectoryPath ) @@ -151,8 +150,7 @@ public: * tree, otherwise only a single version of each part is recognized, namely the * one without the ".revN[N..]" trailer. */ - DIR_LIB_SOURCE( const STRING& aDirectoryPath, const STRING& aOptions = "" ) - throw( IO_ERROR ); + DIR_LIB_SOURCE( const STRING& aDirectoryPath, const STRING& aOptions = "" ) throw( IO_ERROR ); ~DIR_LIB_SOURCE(); @@ -187,6 +185,10 @@ public: * will output a debug dump of contents. */ void Show(); + +public: + static void Test( int argc, char** argv ); + #endif }; diff --git a/new/sch_lib_table.cpp b/new/sch_lib_table.cpp index ad1913b274..5dd66ebfe0 100644 --- a/new/sch_lib_table.cpp +++ b/new/sch_lib_table.cpp @@ -25,29 +25,18 @@ #include #include +#include #include -using namespace std; +//using namespace std; // screws up Doxygen using namespace SCH; LIB_TABLE::LIB_TABLE( LIB_TABLE* aFallBackTable ) : fallBack( aFallBackTable ) { - /* not copying fall back, simply search aFallBackTable separately if "logicalName not found". - if( aFallBackTable ) - { - const ROWS& t = aFallBackTable->rows; - - for( ROWS_CITER it = t.begin(); it != t.end(); ++it ) - { - // our rows are empty, expect no collisions here - auto_ptr row( new ROW( *it->second ) ); - row->owner = this; - insert( row ); - } - } - */ + // not copying fall back, simply search aFallBackTable separately + // if "logicalName not found". } @@ -56,9 +45,9 @@ void LIB_TABLE::Parse( SCH_LIB_TABLE_LEXER* in ) throw( IO_ERROR ) /* grammar: (lib_table - (lib (logical "LOGICAL")(type "TYPE")(full_uri "FULL_URI")(options "OPTIONS")) - (lib (logical "LOGICAL")(type "TYPE")(full_uri "FULL_URI")(options "OPTIONS")) - (lib (logical "LOGICAL")(type "TYPE")(full_uri "FULL_URI")(options "OPTIONS")) + (lib (logical LOGICAL)(type TYPE)(full_uri FULL_URI)(options OPTIONS)) + (lib (logical LOGICAL)(type TYPE)(full_uri FULL_URI)(options OPTIONS)) + (lib (logical LOGICAL)(type TYPE)(full_uri FULL_URI)(options OPTIONS)) ) note: "(lib_table" has already been read in. @@ -66,7 +55,7 @@ void LIB_TABLE::Parse( SCH_LIB_TABLE_LEXER* in ) throw( IO_ERROR ) ELT_T tok; - while( (tok = in->NextTok() ) != T_RIGHT && tok != T_EOF ) + while( ( tok = in->NextTok() ) != T_RIGHT && tok != T_EOF ) { // (lib (logical "LOGICAL")(type "TYPE")(full_uri "FULL_URI")(options "OPTIONS")) @@ -83,7 +72,7 @@ void LIB_TABLE::Parse( SCH_LIB_TABLE_LEXER* in ) throw( IO_ERROR ) in->NeedSYMBOLorNUMBER(); - auto_ptr row( new ROW( this ) ); + std::auto_ptr row( new ROW( this ) ); row->SetLogicalName( in->CurText() ); @@ -127,12 +116,12 @@ void LIB_TABLE::Parse( SCH_LIB_TABLE_LEXER* in ) throw( IO_ERROR ) row->SetOptions( in->CurText() ); in->NeedRIGHT(); - in->NeedRIGHT(); // teriminate the (lib..) + in->NeedRIGHT(); // terminate the (lib..) // all logicalNames within this table fragment must be unique, so we do not - // replace. However a fallBack table can have a conflicting logicalName - // and ours will supercede that one since in FindLib() we search this table - // before any fall back. + // use doReplace in InsertRow(). However a fallBack table can have a + // conflicting logicalName and ours will supercede that one since in + // FindLib() we search this table before any fall back. if( !InsertRow( row ) ) { STRING msg; @@ -144,7 +133,6 @@ void LIB_TABLE::Parse( SCH_LIB_TABLE_LEXER* in ) throw( IO_ERROR ) throw IO_ERROR( msg ); } } - return; } @@ -172,12 +160,13 @@ void LIB_TABLE::ROW::Format( OUTPUTFORMATTER* out, int nestLevel ) const STRINGS LIB_TABLE::GetLogicalLibs() { - // only return unique logical library names. Use std::set::insert() to - // quietly reject any duplicates, which can happen in the fall back table(s). - set unique; - STRINGS ret; + // Only return unique logical library names. Use std::set::insert() to + // quietly reject any duplicates, which can happen when encountering a duplicate + // logical lib name from one of the fall back table(s). - const LIB_TABLE* cur = this; + std::set unique; + STRINGS ret; + const LIB_TABLE* cur = this; do { @@ -188,8 +177,8 @@ STRINGS LIB_TABLE::GetLogicalLibs() } while( ( cur = cur->fallBack ) != 0 ); - // return a sorted, unique set of STRINGS to caller - for( set::const_iterator it = unique.begin(); it!=unique.end(); ++it ) + // return a sorted, unique set of logical lib name STRINGS to caller + for( std::set::const_iterator it = unique.begin(); it!=unique.end(); ++it ) ret.push_back( *it ); return ret; @@ -228,7 +217,7 @@ const LIB_TABLE::ROW* LIB_TABLE::FindRow( const STRING& aLogicalName ) const } -bool LIB_TABLE::InsertRow( auto_ptr& aRow, bool doReplace ) +bool LIB_TABLE::InsertRow( std::auto_ptr& aRow, bool doReplace ) { // this does not need to be super fast. diff --git a/new/sch_lib_table.h b/new/sch_lib_table.h index 7e42b4e3b0..5b860a37c1 100644 --- a/new/sch_lib_table.h +++ b/new/sch_lib_table.h @@ -41,6 +41,41 @@ class PART; * Class LIB_TABLE * holds LIB_TABLE::ROW records, and can be searched in a very high speed * way based on logical library name. + *

+ * This class owns the library table, which is like fstab in concept and maps logical + * library name to library URI, type, and options. It has the following columns: + *

    + *
  • Logical Library Name + *
  • Library Type + *
  • Library URI. The full URI to the library source, form dependent on Type. + *
  • Options, used for access, such as password + *
+ *

+ * The Library Type can be one of: + *

    + *
  • "dir" + *
  • "schematic" i.e. a parts list from another schematic. + *
  • "subversion" + *
  • "http" + *
+ *

+ * For now, the Library URI types needed to support the various types can be one of those + * shown below, which are typical of each type: + *

    + *
  • "file://C:/mylibdir" + *
  • "file://home/user/kicadwork/jtagboard.sch" + *
  • "svn://kicad.org/partlib/trunk" + *
  • "http://kicad.org/partlib" + *
+ *

+ * The applicable library table is built up from several additive rows (table fragments), + * and the final table is a (conceptual) merging of the table fragments. Two + * anticipated sources of the rows are a personal table, and a schematic resident + * table. The schematic resident table rows are considered a higher priority in + * the final dynamically assembled library table. A row in the schematic + * contribution to the library table takes precedence over the personal table + * if there is a collision on logical library name, otherwise the rows simply + * combine without issue to make up the applicable library table. * * @author Dick Hollenbeck */ @@ -168,7 +203,8 @@ public: /** * Constructor LIB_TABLE - * builds a library table from an s-expression form of the library table. + * builds a library table by pre-pending this table fragment in front of + * @a aFallBackTable. Loading of this table fragment is done by using Parse(). * @param aFallBackTable is another LIB_TABLE which is searched only when * a record is not found in this table. No ownership is taken of aFallBackTable. */ @@ -176,16 +212,15 @@ public: /** * Function Parse - * fills this object from information in the input stream \a aLexer, which + * fills this table fragment from information in the input stream \a aLexer, which * is a DSNLEXER customized for the grammar needed to describe instances of this object. * The entire textual element spec is
- * (lib_table (logical _yourfieldname_)(value _yourvalue_) visible)) * *

      * (lib_table
-     *   (lib (logical "LOGICAL")(type "TYPE")(fullURI "FULL_URI")(options "OPTIONS"))
-     *   (lib (logical "LOGICAL")(type "TYPE")(fullURI "FULL_URI")(options "OPTIONS"))
-     *   (lib (logical "LOGICAL")(type "TYPE")(fullURI "FULL_URI")(options "OPTIONS"))
+     *   (lib (logical LOGICAL)(type TYPE)(fullURI FULL_URI)(options OPTIONS))
+     *   (lib (logical LOGICAL)(type TYPE)(fullURI FULL_URI)(options OPTIONS))
+     *   (lib (logical LOGICAL)(type TYPE)(fullURI FULL_URI)(options OPTIONS))
      * 
* * When this function is called, the input token stream given by \a aLexer @@ -193,7 +228,7 @@ public: * identifying keyword and before the content specifying stuff.
* (lib_table ^ (....) ) * - * @param aSpec is the input token stream of keywords and symbols. + * @param aLexer is the input token stream of keywords and symbols. */ void Parse( SCH_LIB_TABLE_LEXER* aLexer ) throw( IO_ERROR ); diff --git a/new/sch_libs.cpp.notused b/new/sch_libs.cpp.notused new file mode 100644 index 0000000000..4eb12cbceb --- /dev/null +++ b/new/sch_libs.cpp.notused @@ -0,0 +1,15 @@ + + + +/** + * Function GetPart + * finds and loads a PART, and parses it. As long as the part is + * accessible in any LIB_SOURCE, opened or not opened, this function + * will find it and load it into its containing LIB, even if that means + * having to load a new LIB as given in the library table. + */ +static PART* SCH_LIBS::GetPart( const LPID& aLogicalPartID ) throw( IO_ERROR ) +{ +} + + diff --git a/new/sch_libs.h.notused b/new/sch_libs.h.notused new file mode 100644 index 0000000000..bc0a90a9a4 --- /dev/null +++ b/new/sch_libs.h.notused @@ -0,0 +1,81 @@ +/* + * This program source code file is part of KICAD, a free EDA CAD application. + * + * Copyright (C) 2010-2011 SoftPLC Corporation, Dick Hollenbeck + * Copyright (C) 2010 Kicad Developers, see change_log.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef SCH_LIBS_H_ +#define SCH_LIBS_H_ + +#include + + +/** + * Class LIBS + * houses a handful of functions that manage all the RAM resident LIBs, and + * provide for a global part lookup function, GetPart(), which can be the basis + * of a cross LIB hyperlink. + */ +class LIBS +{ +public: + + /** + * Function GetPart + * finds and loads a PART, and parses it. As long as the part is + * accessible in any LIB_SOURCE, opened or not opened, this function + * will find it and load it into its containing LIB, even if that means + * having to load a new LIB as given in the library table. + */ + static PART* GetPart( const LPID& aLogicalPartID ) throw( IO_ERROR ); + + /** + * Function GetLib + * is first a lookup function and then if needed, a factory function. + * If aLogicalLibraryName has been opened, then return the already opened + * LIB. If not, then instantiate the library and fill the initial + * library PARTs (unparsed) and categories, and add it to LIB::libraries + * for future reference. + */ + static LIB* GetLib( const STRING& aLogicalLibraryName ) throw( IO_ERROR ); + + /** + * Function GetOpenedLibNames + * returns the logical library names of LIBs that are already opened. + * @see LPID::GetLogicalLibraries() + */ + static STRINGS GetOpendedLogicalLibNames(); + + /** + * Function CloseLibrary + * closes an open library @a aLibrary and removes it from class LIBS. + */ + static void CloseLibrary( LIB* aLibrary ) throw( IO_ERROR ); + + +private: + + /// collection of LIBs, searchable by logical name. + static std::map< STRING, LIB* > libraries; // owns the LIBs. +}; + + +#endif // SCH_LIBS_H_ diff --git a/new/sch_lpid.cpp b/new/sch_lpid.cpp index fc27025468..5d73680963 100644 --- a/new/sch_lpid.cpp +++ b/new/sch_lpid.cpp @@ -22,15 +22,248 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ +#include #include using namespace SCH; - -LPID::LPID( const STRING& aLPID ) throw( PARSE_ERROR ) +static inline bool isDigit( char c ) { + return c >= '0' && c <= '9'; +} + +const char* EndsWithRev( const char* start, const char* tail, char separator ) +{ + bool sawDigit = false; + + while( tail > start && isDigit( *--tail ) ) + { + sawDigit = true; + } + + // if sawDigit, tail points to the 'v' here. + + if( sawDigit && tail-3 >= start ) + { + tail -= 3; + + if( tail[0]==separator && tail[1]=='r' && tail[2]=='e' && tail[3]=='v' ) + { + return tail+1; // omit separator, return "revN[N..]" + } + } + + return 0; } +LPID::LPID( const STRING& aLPID ) throw( PARSE_ERROR ) +{ + const char* rev = EndsWithRev( aLPID ); + size_t revNdx; + size_t partNdx; + size_t baseNdx; + //============================================== + if( rev ) + { + revNdx = rev - aLPID.c_str(); + revision = aLPID.substr( revNdx ); + --revNdx; // back up to omit the '/' which preceeds the rev + } + else + revNdx = aLPID.size(); + + //=============================================== + if( ( partNdx = aLPID.find( ':' ) ) != aLPID.npos ) + { + logical = aLPID.substr( 0, partNdx ); + ++partNdx; // skip ':' + } + else + partNdx = 0; + + //=================================== + // "length limited" search: + const char* base = (const char*) memchr( aLPID.c_str() + partNdx, '/', revNdx - partNdx ); + + if( base ) + { + baseNdx = base - aLPID.c_str(); + category = aLPID.substr( partNdx, baseNdx - partNdx ); + ++baseNdx; // skip '/' + } + else + { + baseNdx = partNdx; + } + + //=============================================== + baseName = aLPID.substr( baseNdx, revNdx - baseNdx ); +} + + +STRING LPID::GetLogicalLib() const +{ + return logical; +} + + +bool LPID::SetLogicalLib( const STRING& aLogical ) +{ + if( aLogical.find_first_of( ":/" ) == STRING::npos ) + { + logical = aLogical; + return true; + } + return false; +} + + +STRING LPID::GetCategory() const +{ + return category; +} + + +bool LPID::SetCategory( const STRING& aCategory ) +{ + if( aCategory.find_first_of( ":/" ) == STRING::npos ) + { + category = aCategory; + return true; + } + return false; +} + + +STRING LPID::GetBaseName() const +{ + return baseName; +} + + +bool LPID::SetBaseName( const STRING& aBaseName ) +{ + if( aBaseName.find_first_of( ":/" ) == STRING::npos ) + { + baseName = aBaseName; + return true; + } + return false; +} + + +STRING LPID::GetPartName() const +{ + STRING ret; + + // return [category/]baseName + if( category.size() ) + { + ret += category; + ret += '/'; + } + + ret += baseName; + + return ret; +} + + +STRING LPID::GetRevision() const +{ + return revision; +} + + +bool LPID::SetRevision( const STRING& aRevision ) +{ + STRING rev; + + rev += "x/"; + rev += aRevision; + + if( EndsWithRev( rev ) ) + { + revision = aRevision; + return true; + } + return false; +} + + +STRING LPID::GetFullText() const +{ + STRING ret; + + if( logical.size() ) + { + ret += logical; + ret += ':'; + } + + if( category.size() ) + { + ret += category; + ret += '/'; + } + + ret += baseName; + + if( revision.size() ) + { + ret += '/'; + ret += revision; + } + + return ret; +} + + +#if 1 && defined(DEBUG) + +// build this with Debug CMAKE_BUILD_TYPE + +void LPID::Test() +{ + static const char* lpids[] = { + "me:passives/R/rev0", + "passives/R/rev2", + ":passives/R/rev3", + "C/rev22", + "passives/C22", + "R", + "me:R", + // most difficult: + "me:/R/rev0", + "me:R/rev0", + }; + + for( unsigned i=0; i - +#include // STRING /** * Class LPID @@ -45,42 +44,7 @@ *
  • "rev6" is the revision number, which is optional. If missing then its * delimiter should also not be present. * - *

    - * This class owns the library table, which is like fstab in concept and maps logical - * library name to library URI, type, and options. It has the following columns: - *

      - *
    • Logical Library Name - *
    • Library Type - *
    • Library URI. The full URI to the library source, form dependent on Type. - *
    • Options, used for access, such as password - *
    - *

    - * For now, the Library Type can be one of: - *

      - *
    • "dir" - *
    • "schematic" i.e. a parts list from another schematic. - *
    • "subversion" - *
    • "bazaar" - *
    • "http" - *
    - *

    - * For now, the Library URI types needed to support the various types can be one of those - * shown below, which are typical of each type: - *

      - *
    • "file://C:/mylibdir" - *
    • "file://home/user/kicadwork/jtagboard.sch" - *
    • "svn://kicad.org/partlib/trunk" - *
    • "http://kicad.org/partlib" - *
    - *

    - * The applicable library table is built up from several additive rows (table fragments), - * and the final table is a merging of the table fragments. Two anticipated sources of - * the rows are a personal table, and a schematic resident table. The schematic - * resident table rows are considered a higher priority in the final dynamically - * assembled library table. A row in the schematic contribution to the library table - * will take precedence over the personal table if there is a collision on logical - * library name, otherwise the rows simply combine without issue to make up the - * applicable library table. + * @author Dick Hollenbeck */ class LPID // aka GUID { @@ -95,12 +59,19 @@ public: LPID( const STRING& aLPID ) throw( PARSE_ERROR ); /** - * Function GetLogLib + * Function GetLogicalLib * returns the logical library portion of a LPID. There is not Set accessor * for this portion since it comes from the library table and is considered * read only here. */ - STRING GetLogLib() const; + STRING GetLogicalLib() const; + + /** + * Function SetCategory + * overrides the logical lib name portion of the LPID to @a aLogical, and can be empty. + * @return bool - true unless parameter has ':' or '/' in it. + */ + bool SetLogicalLib( const STRING& aLogical ); /** * Function GetCategory @@ -113,8 +84,35 @@ public: * Function SetCategory * overrides the category portion of the LPID to @a aCategory and is typically * either the empty string or a single word like "passives". + * @return bool - true unless parameter has ':' or '/' in it. + */ + bool SetCategory( const STRING& aCategory ); + + /** + * Function GetBaseName + * returns the part name without the category. + */ + STRING GetBaseName() const; + + /** + * Function SetBaseName + * overrides the base name portion of the LPID to @a aBaseName + * @return bool - true unless parameter has ':' or '/' in it. + */ + bool SetBaseName( const STRING& aBaseName ); + + /** + * Function GetBaseName + * returns the part name, i.e. category/baseName without revision. + */ + STRING GetPartName() const; + + /** + * Function SetBaseName + * overrides the part name portion of the LPID to @a aPartName + not really needed, partname is an agreggate anyway, just parse a new one. + void SetPartName( const STRING& aPartName ); */ - void SetCategory( const STRING& aCategory ); /** * Function GetRevision @@ -126,14 +124,43 @@ public: * Function SetRevision * overrides the revision portion of the LPID to @a aRevision and must * be in the form "rev" where "" is "1", "2", etc. + * @return bool - true unless parameter is not of the form "revN]N..]" */ - void SetRevision( const STRING& aRevision ); + bool SetRevision( const STRING& aRevision ); /** * Function GetFullText * returns the full text of the LPID. */ STRING GetFullText() const; + +#if defined(DEBUG) + static void Test(); +#endif + +protected: + STRING logical; ///< logical lib name or empty + STRING category; ///< or empty + STRING baseName; ///< excludes category + STRING revision; ///< "revN[N..]" or empty }; + +/** + * Function EndsWithRev + * returns a pointer to the final string segment: "revN[N..]" or NULL if none. + * @param start is the beginning of string segment to test, the partname or + * any middle portion of it. + * @param tail is a pointer to the terminating nul, or one past inclusive end of + * segment, i.e. the string segment of interest is [start,tail) + * @param separator is the separating byte, expected: '.' or '/', depending on context. + */ +const char* EndsWithRev( const char* start, const char* tail, char separator = '/' ); + +static inline const char* EndsWithRev( const STRING& aPartName, char separator = '/' ) +{ + return EndsWithRev( aPartName.c_str(), aPartName.c_str()+aPartName.size(), separator ); +} + + #endif // SCH_LPID_H_