s3e: fix build break
[calu.git] / 3b_sim / SReadline / SReadline.h
1 /*!
2     @file  SReadline.h
3     @brief C++ wrapper around libreadline.
4            Supported: editing, history, custom completers, keymaps.
5            Attention: implementation is not thread safe!
6                       It is mainly because the readline library provides
7                       pure C interface and has many calls for an "atomic" 
8                       completion operation
9  */
10 //
11 // Date:      17 December 2005
12 //            03 April    2006
13 //            20 April    2006
14 //            07 May      2006
15 //
16 // Copyright (c) Sergey Satskiy 2005 - 2006
17 //               <sergesatsky@yahoo.com>
18 //
19 // Permission to copy, use, modify, sell and distribute this software 
20 // is granted provided this copyright notice appears in all copies. 
21 // This software is provided "as is" without express or implied
22 // warranty, and with no claim as to its suitability for any purpose.
23 //
24
25 #ifndef SREADLINE_H
26 #define SREADLINE_H
27
28 #include <stdio.h>
29
30 #include <readline/readline.h>
31 #include <readline/history.h>
32 #include <readline/keymaps.h>
33
34 #include <string>
35 #include <fstream>
36 #include <vector>
37 #include <stdexcept>
38 #include <map>
39
40 #include <boost/algorithm/string/trim.hpp>
41 #include <boost/tokenizer.hpp>
42 #include <boost/function.hpp>
43
44
45     /*! @brief Used to avoid linking errors in case
46                of including into many compilation units
47      */
48 namespace
49 {
50         //! @brief Tokens in a single variation of a user command
51     typedef std::vector< std::string >              TokensStorage;
52         //! @brief Set of variations of user commands
53     typedef std::vector< TokensStorage >            CompletionsStorage;
54         //! @brief Pressed key callback. Must return 0 if OK, != 0 otherwise
55     typedef boost::function< int ( int, int ) >     KeyCallback;
56         //! @brief A set of keys binding
57     typedef std::map< int, KeyCallback >            KeysBind;
58
59     
60     const size_t                    DefaultHistoryLimit( 64 );  //!< Default value for the history length
61     CompletionsStorage              Completions;                //!< Global storage of custom completions
62     TokensStorage                   Tokens;                     //!< Tokens storage for a single completion session
63     std::map< Keymap, KeysBind >    Keymaps;                    //!< Global storage for keymaps
64
65     bool                            KeymapWasSetup( false );    //!< Has sense if a keymap was
66                                                                 //!< setup before the first readline call
67     Keymap                          Earlykeymap( 0 );           //!< The keymap which was setup before the first readline call
68
69
70         /*! @brief Custom completion generator
71             @param text Pointer to a token to be completed
72             @param State 0 for a first call, non 0 for all consequent calls
73          */
74     char * Generator( const char *  text, int  State );
75
76     
77         /*! @brief The function is called before trying to complete a token
78             @param text A token to be completed
79             @param start Index of the beginning of the token in the readline buffer
80             @param end   Index of the end of the token in the readline buffer
81          */
82     char ** UserCompletion( const char * text,
83                             int          start,
84                             int          end );
85
86
87         /*! @brief The function selects the set of bindings and 
88                    makes the corresponding call.
89             @param Count The parameter is passed by readline
90             @param Key   The pressed key
91          */
92     int  KeyDispatcher( int  Count, int  Key );
93
94
95         /*! @brief The readline startup hook. It is required to setup the proper keymap.
96          */
97     int  StartupHook( void );
98
99     
100         /*! @brief Compares all the Input tokens with starts tokens in the Pattern
101             @param Pattern pattern tokens
102             @param Input user input tokens
103             @return true if first Input.size() tokens are equal to the pattern tokens
104          */
105     template < typename  Container >
106     bool AreTokensEqual( const Container &  Pattern, 
107                          const Container &  Input )
108     {
109         if ( Input.size() > Pattern.size() ) return false;
110
111         typename Container::const_iterator  k( Pattern.begin() );
112         typename Container::const_iterator  j( Input.begin() );
113         for ( ; j != Input.end(); ++k, ++j )
114         {
115             if ( *k == "%file" ) continue;
116             if ( *k != *j )      return false;
117         }
118         return true;
119     }
120
121
122         // See description near the prototype
123     template < typename  ContainerType >
124     void SplitTokens( const std::string &  Source, ContainerType &  Container )
125     {
126         typedef boost::tokenizer< boost::char_separator< char > >       TokenizerType;
127
128         boost::char_separator<char>     Separators( " \t\n" );              // Set of token separators
129         TokenizerType                   Tokenizer( Source, Separators );    // Tokens provider
130         std::string                     SingleToken;                        // Temporary storage for a token
131         
132         Container.clear();
133         for ( TokenizerType::const_iterator  k( Tokenizer.begin() ); k != Tokenizer.end(); ++k )
134         {
135             SingleToken = *k;
136             boost::algorithm::trim( SingleToken );
137             Container.push_back( SingleToken );
138         }
139     }
140
141     
142         // See description near the prototype
143     char ** UserCompletion( const char *  text, int  start, int  /*end*/ )
144     {
145             // No default completion at all
146         rl_attempted_completion_over = 1;
147
148         if ( Completions.empty() )  return NULL;
149
150             // Memorize all the previous tokens
151         std::string     PreInput( rl_line_buffer, start );
152         SplitTokens( PreInput, Tokens );
153
154
155             // Detect whether we should call the standard file name completer or a custom one
156         bool    FoundPretender( false );
157
158         for ( CompletionsStorage::const_iterator  k( Completions.begin() ); k != Completions.end(); ++k )
159         {
160             if ( ! AreTokensEqual( *k, Tokens ) ) continue;
161
162             if ( (*k).size() > Tokens.size() )
163             {
164                 FoundPretender = true;
165                 if ( (*k)[ Tokens.size() ] == "%file" )
166                 {
167                         // Standard file name completer - called for the "%file" keyword
168                     return rl_completion_matches( text, rl_filename_completion_function );
169                 }
170             }
171         }
172         
173         if ( FoundPretender )
174         {
175             return rl_completion_matches( text, Generator );
176         }
177         return NULL;
178     }
179     
180    
181
182         // See description near the prototype
183     char * Generator( const char *  text, int  State )
184     {
185         static int                                  Length;
186         static CompletionsStorage::const_iterator   Iterator;
187
188         
189         if ( State == 0 )
190         {
191             Iterator = Completions.begin();
192             Length = strlen( text );
193         }
194
195         for ( ; Iterator != Completions.end(); ++Iterator )
196         {
197             if ( ! AreTokensEqual( *Iterator, Tokens ) ) continue;
198
199             if ( (*Iterator).size() > Tokens.size() )
200             {
201                 if ( (*Iterator)[ Tokens.size() ] == "%file" ) continue;
202
203             
204                 if ( strncmp( text, (*Iterator)[ Tokens.size() ].c_str(), Length ) == 0 )
205                 {
206                         // readline will free the allocated memory
207                     char *  NewString( (char*)malloc( strlen( (*Iterator)[ Tokens.size() ].c_str() ) + 1 ) );
208                     strcpy( NewString, (*Iterator)[ Tokens.size() ].c_str() );
209                     ++Iterator;
210                     return NewString;
211                 }
212             }
213         }
214        
215         return NULL;
216     }
217
218     
219         // See the description near the prototype
220     int  KeyDispatcher( int  Count, int  Key )
221     {
222         std::map< Keymap, KeysBind >::iterator  Set( Keymaps.find( rl_get_keymap() ) );
223         if ( Set == Keymaps.end() )
224         {
225                 // Most probably it happens bacause the header was 
226                 // included into many compilation units and the
227                 // keymap setting calls were made in different files.
228                 // This is the problem of "global" data.
229                 // The storage of all the registered keymaps is in anonymous
230                 // namespace.
231             throw std::runtime_error( "Error selecting a keymap." );
232         }
233
234         (Set->second)[ Key ]( Count, Key );
235         return 0;
236     }
237
238
239         // See the description near the prototype
240     int  StartupHook( void )
241     {
242         if ( KeymapWasSetup )
243         {
244             rl_set_keymap( Earlykeymap );
245         }
246         return 0;
247     }
248
249 }   // Anonymous namespace
250
251
252
253
254     /*! @brief The wrapper namespace. 
255                The namespace is also used for other library elements.
256      */
257 namespace swift 
258 {
259
260         /*! @brief The readline keymap wrapper.
261                    Attention: It is not thread safe!
262                    Supports: key binding, key unbinding
263          */
264     class SKeymap
265     {
266         private:
267             Keymap      keymap;     // Readline keymap
268             
269         public:
270                 /*! @brief Creates a new keymap
271                     @param PrintableBound if true - the printable characters are bound
272                                           if false - the keymap is empty
273                  */                  
274             explicit SKeymap( bool  PrintableBound = false ) : 
275                 keymap( NULL )
276             {
277                 if ( PrintableBound )
278                 {
279                     keymap = rl_make_keymap();      // Printable characters are bound
280                 }
281                 else
282                 {
283                     keymap = rl_make_bare_keymap(); // Empty keymap
284                 }
285
286                 if ( keymap == NULL )
287                 {
288                     throw std::runtime_error( "Cannot allocate keymap." );
289                 }
290
291                     // Register a new keymap in the global list
292                 Keymaps[ keymap ] = KeysBind();
293             }
294             
295                 /*! @brief Creates a new keymap which is a copy of Pattern
296                     @param Pattern A keymap to be copied
297                  */
298             explicit SKeymap( Keymap   Pattern ) :
299                 keymap( rl_copy_keymap( Pattern ) )
300             {
301                 if ( keymap == NULL )
302                 {
303                     throw std::runtime_error( "Cannot allocate keymap." );
304                 }
305                 
306                     // Register a new keymap in the global list
307                 Keymaps[ keymap ] = KeysBind();
308             }
309
310                 /*! @brief Frees the allocated keymap
311                  */
312             ~SKeymap()
313             {
314                     // Deregister the keymap
315                 Keymaps.erase( keymap );
316                 rl_discard_keymap( keymap );
317             }
318
319                 /*! @brief Binds the given key to a function
320                     @param Key A key to be bound
321                     @param Callback A function to be called when the Key is pressed
322                  */
323             void Bind( int  Key, KeyCallback  Callback )
324             {
325                 Keymaps[ keymap ][ Key ] = Callback;
326                 if ( rl_bind_key_in_map( Key, KeyDispatcher, keymap ) != 0 )
327                 {
328                         // Remove from the map just bound key
329                     Keymaps[ keymap ].erase( Key );
330                     throw std::runtime_error( "Invalid key." );
331                 }
332             }
333
334
335                 /*! @brief Unbinds the given key
336                     @param Key A key to be unbound
337                  */
338             void Unbind( int  Key )
339             {
340                 rl_unbind_key_in_map( Key, keymap );
341                 Keymaps[ keymap ].erase( Key );
342             }
343
344             // void Bind( const std::string &  Sequence, boost::function< int ( int, int ) > );
345             // void Unbind( std::string &  Sequence );
346
347         public:
348                 /*! @brief Copy constructor
349                     @param rhs Right hand side object of SKeymap
350                  */
351             SKeymap( const SKeymap &  rhs ) : keymap(0)
352             {
353                 if ( this == & rhs )
354                 {
355                     return;
356                 }
357                 keymap = rl_copy_keymap( rhs.keymap );
358             }
359
360                 /*! @brief operator=
361                     @param rhs Right hand side object of SKeymap
362                  */
363             SKeymap &  operator=( const SKeymap &  rhs )
364             {
365                 if ( this == & rhs )
366                 {
367                     return *this;
368                 }
369                 keymap = rl_copy_keymap( rhs.keymap );
370                 return *this;
371             }
372             
373             friend class SReadline;
374     };
375     
376
377         /*! @brief The readline library wrapper.
378                    Attention: It is not thread safe!
379                    Supports: editing, history, custom completers
380          */
381     class SReadline
382     {
383         public:
384                 /*! @brief Constructs the object, sets the completion function
385                     @param Limit History size
386                  */
387             SReadline( const size_t  Limit = DefaultHistoryLimit ) : 
388                 HistoryLimit( Limit ),
389                 HistoryFileName( "" ), 
390                 OriginalCompletion( rl_attempted_completion_function )
391             {
392                 rl_startup_hook = StartupHook;
393                 rl_attempted_completion_function = UserCompletion;
394                 using_history();
395             }
396             
397                 /*! @brief Constructs the object, sets the completion function, loads history
398                     @param historyFileName File name to load history from
399                     @param Limit History size
400                  */
401             SReadline( const std::string &  historyFileName,
402                        const size_t  Limit = DefaultHistoryLimit ) :
403                 HistoryLimit( Limit ),
404                 HistoryFileName( historyFileName ),
405                 OriginalCompletion( rl_attempted_completion_function )
406             {
407                 rl_startup_hook = StartupHook;
408                 rl_attempted_completion_function = UserCompletion;
409                 using_history();
410                 LoadHistory( HistoryFileName );
411             }
412
413                 /*! @brief Saves the session history (if the file name was provided) 
414                            and destroys the object
415                  */
416             ~SReadline()
417             {
418                 rl_attempted_completion_function = OriginalCompletion;
419                 SaveHistory( HistoryFileName );
420             }
421
422                 /*! @brief Gets a single line from a user
423                     @param Prompt A printed prompt
424                     @return A string which was actually inputed
425                  */
426             std::string  GetLine( const std::string &  Prompt )
427             {
428                 bool    Unused;
429                 return GetLine( Prompt, Unused );
430             }
431             
432
433                 /*! @brief Gets a single line from a user
434                     @param Prompt A printed prompt
435                     @param ReadTokens A user inputed string splitted into tokens. 
436                            The container is cleared first
437                     @return A string which was actually inputed
438                  */
439             template < typename  Container >
440             std::string  GetLine( const std::string &  Prompt,
441                                   Container &          ReadTokens )
442             {
443                 bool    Unused;
444                 return GetLine( Prompt, ReadTokens, Unused );
445             }
446
447
448                 /*! @brief Gets a single line from a user
449                     @param Prompt A printed prompt
450                     @param BreakOut it is set to true if the EOF found
451                     @param ReadTokens A user inputed string splitted into tokens.
452                            The container is cleared first
453                     @return A string which was actually inputed
454                  */
455             template < typename  Container >
456             std::string  GetLine( const std::string &  Prompt,
457                                   Container &          ReadTokens,
458                                   bool &               BreakOut )
459             {
460                 std::string     Input( GetLine( Prompt, BreakOut ) );
461                 SplitTokens( Input, ReadTokens );
462                 return Input;
463             }
464
465
466                 /*! @brief Gets a single line from a user
467                     @param Prompt A printed prompt
468                     @param BreakOut it is set to true if the EOF found
469                     @return A string which was actually inputed
470                  */
471             std::string  GetLine( const std::string &  Prompt, 
472                                   bool &               BreakOut )
473             {
474                 BreakOut = true;
475
476                 char *  ReadLine( readline( Prompt.c_str() ) );
477                 if ( ReadLine == NULL ) return std::string();
478                 
479                     // It's OK
480                 BreakOut = false;
481                 std::string     Input( ReadLine );
482                 free( ReadLine );
483
484                 boost::algorithm::trim( Input );
485                 if ( !Input.empty() )
486                 {
487                     if ( (history_length == 0) ||
488                          (Input != history_list()[ history_length - 1 ]->line)
489                        )
490                     {
491                         add_history( Input.c_str() );
492                         if ( history_length >= static_cast< int >( HistoryLimit ) )
493                         {
494                             stifle_history( HistoryLimit );
495                         }
496                     }
497                 }
498
499                 return Input;
500             }
501
502
503                 /*! @brief Fills the given container with the current history list
504                            Does not clear the given container
505                  */
506             template < typename ContainerType >
507             void GetHistory( ContainerType &  Container )
508             {
509                 for ( int  k( 0 ); k < history_length; ++k )
510                 {
511                     Container.push_back( history_list()[ k ]->line );
512                 }
513             }
514
515                 /*! @brief Saves the history to the given file stream
516                     @param OS output file stream
517                     @return true if success
518                  */
519             bool SaveHistory( std::ostream & OS )
520             {
521                 if ( ! OS ) return false;
522                 for ( int  k( 0 ); k < history_length; ++k )
523                 {
524                     OS << history_list()[ k ]->line << std::endl;
525                 }
526                 return true;
527             }
528
529                 /*! @brief Saves the history to the given file
530                     @param FileName File name to save the history to
531                     @return true if success
532                  */
533             bool SaveHistory( const std::string &  FileName )
534             {
535                 if ( FileName.empty() ) return false;
536
537                 std::ofstream  OS( FileName.c_str() );
538                 return SaveHistory( OS );
539             }
540
541                 /*! @brief Clears the history. Does not affect the file where
542                            the previous session history is saved.
543                  */
544             void ClearHistory()
545             {
546                 clear_history();
547             }
548             
549
550                 /*! @brief Loads a history from a file stream
551                     @param IS Input file stream
552                     @return true if success
553                  */
554             bool LoadHistory( std::istream &  IS )
555             {
556                 if ( ! IS ) return false;
557
558                 ClearHistory();
559                 std::string     OneLine;
560
561                 while ( ! getline( IS, OneLine ).eof() )
562                 {
563                     boost::algorithm::trim( OneLine );
564                     if ( (history_length == 0) ||
565                          (OneLine != history_list()[ history_length - 1 ]->line)
566                        )
567                     {
568                         add_history( OneLine.c_str() );
569                     }
570                 }
571                 stifle_history( HistoryLimit );
572                 return true;
573             }
574             
575                 /*! @brief Loads a history from the given file
576                     @param FileName File name to be load from
577                     @return true if success
578                  */
579             bool LoadHistory( const std::string &  FileName )
580             {
581                 if ( FileName.empty() ) return false;
582
583                 std::ifstream   IS( FileName.c_str() );
584                 return LoadHistory( IS );
585             }
586
587                 /*! @brief Allows to register custom completers.
588                            Supports a special keyword: %file. It means to use the standard file name completer
589                            For example the given container elements could be as follows:
590                                command1 opt1
591                                command1 opt2 %file
592                                command2
593                                command2 opt1
594                            Each container element must describe a single possible command line
595                            The container element must have a conversion to std::string operator
596                     @param Container A container which has all the user possible commands
597                  */       
598             template < typename  ContainerType >
599             void RegisterCompletions( const ContainerType &  Container )
600             {
601                 Completions.clear();
602                 for ( typename ContainerType::const_iterator  k( Container.begin() );
603                       k != Container.end(); ++k )
604                 {
605                     std::vector< std::string >  OneLine;
606                     SplitTokens( (*k).get<0>(), OneLine );
607                     Completions.push_back( OneLine );
608                 }
609             }
610
611                 
612                 /*! @brief Sets the given keymap
613                     @param NewKeymap The keymap that should be used from now
614                  */
615             void  SetKeymap( SKeymap &  NewKeymap )
616             {
617                 rl_set_keymap( NewKeymap.keymap );
618                 KeymapWasSetup = true;
619                 Earlykeymap = NewKeymap.keymap;
620             }
621
622         private:
623             const size_t            HistoryLimit;       //!< Constructor accepted or default
624             const std::string       HistoryFileName;    //!< Original constructor accepted
625             rl_completion_func_t *  OriginalCompletion; //!< Original state will be restored
626     };
627
628
629 } // namespace swift
630
631 #endif 
632