StdAir Logo  1.00.0
C++ Standard Airline IT Object Library
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
SReadline.hpp
Go to the documentation of this file.
1 
11 //
12 // Date: 17 December 2005
13 // 03 April 2006
14 // 20 April 2006
15 // 07 May 2006
16 //
17 // Copyright (c) Sergey Satskiy 2005 - 2006
18 // <sergesatsky@yahoo.com>
19 //
20 // Permission to copy, use, modify, sell and distribute this software
21 // is granted provided this copyright notice appears in all copies.
22 // This software is provided "as is" without express or implied
23 // warranty, and with no claim as to its suitability for any purpose.
24 //
25 
26 #ifndef SREADLINE_H
27 #define SREADLINE_H
28 
29 #include <stdio.h>
30 
31 #include <readline/readline.h>
32 #include <readline/history.h>
33 #include <readline/keymaps.h>
34 
35 #include <string>
36 #include <fstream>
37 #include <vector>
38 #include <stdexcept>
39 #include <map>
40 
41 #include <boost/algorithm/string/trim.hpp>
42 #include <boost/tokenizer.hpp>
43 #include <boost/function.hpp>
44 
45 
50 namespace {
54  typedef std::vector<std::string> TokensStorage;
55 
59  typedef std::vector<TokensStorage> CompletionsStorage;
60 
64  typedef boost::function<int (int, int)> KeyCallback;
65 
69  typedef std::map<int, KeyCallback> KeysBind;
70 
74  const size_t DefaultHistoryLimit (64);
75 
79  CompletionsStorage Completions;
80 
84  TokensStorage Tokens;
85 
89  std::map<Keymap, KeysBind> Keymaps;
90 
94  bool KeymapWasSetup (false);
95 
99  Keymap Earlykeymap (0);
100 
101 
108  char* Generator (const char* text, int State);
109 
110 
118  char** UserCompletion (const char* text, int start, int end);
119 
120 
128  int KeyDispatcher (int Count, int Key);
129 
130 
135  int StartupHook (void);
136 
137 
145  template <typename Container>
146  bool AreTokensEqual (const Container& Pattern, const Container& Input) {
147  if (Input.size() > Pattern.size()) {
148  return false;
149  }
150 
151  typename Container::const_iterator k (Pattern.begin());
152  typename Container::const_iterator j (Input.begin());
153  for ( ; j != Input.end(); ++k, ++j) {
154  const std::string lPattern = *k;
155  if (lPattern == "%file") {
156  continue;
157  }
158 
159  const std::string lInput = *j;
160  if (lPattern != lInput) {
161  return false;
162  }
163  }
164  return true;
165  }
166 
167  // See description near the prototype
168  template <typename ContainerType>
169  void SplitTokens (const std::string& Source, ContainerType& Container) {
170  typedef boost::tokenizer<boost::char_separator<char> > TokenizerType;
171 
172  // Set of token separators
173  boost::char_separator<char> Separators (" \t\n");
174  // Tokens provider
175  TokenizerType Tokenizer (Source, Separators);
176 
177  Container.clear();
178  for (TokenizerType::const_iterator k (Tokenizer.begin());
179  k != Tokenizer.end(); ++k) {
180  // Temporary storage for the token, in order to trim that latter
181  std::string SingleToken (*k);
182 
183  boost::algorithm::trim (SingleToken);
184  Container.push_back (SingleToken);
185  }
186  }
187 
188  // See description near the prototype
189  char** UserCompletion (const char* text, int start, int end) {
190  // No default completion at all
191  rl_attempted_completion_over = 1;
192 
193  if (Completions.empty() == true) {
194  return NULL;
195  }
196 
197  // Memorise all the previous tokens
198  std::string PreInput (rl_line_buffer, start);
199  SplitTokens (PreInput, Tokens);
200 
201  // Detect whether we should call the standard file name completer
202  // or a custom one
203  bool FoundPretender (false);
204 
205  for (CompletionsStorage::const_iterator k (Completions.begin());
206  k != Completions.end(); ++k) {
207  const TokensStorage& lTokenStorage = *k;
208  if (AreTokensEqual (lTokenStorage, Tokens) == false) {
209  continue;
210  }
211 
212  if (lTokenStorage.size() > Tokens.size()) {
213  FoundPretender = true;
214  if (lTokenStorage [Tokens.size()] == "%file") {
215  // Standard file name completer - called for the "%file" keyword
216  return rl_completion_matches (text, rl_filename_completion_function);
217  }
218  }
219  }
220 
221  if (FoundPretender) {
222  return rl_completion_matches (text, Generator);
223  }
224  return NULL;
225  }
226 
227  // See description near the prototype
228  char* Generator (const char* text, int State) {
229  static int Length;
230  static CompletionsStorage::const_iterator Iterator;
231 
232  if ( State == 0 ) {
233  Iterator = Completions.begin();
234  Length = strlen (text);
235  }
236 
237  for ( ; Iterator != Completions.end(); ++Iterator) {
238  const TokensStorage& lCompletion = *Iterator;
239  if (AreTokensEqual (lCompletion, Tokens) == false) {
240  continue;
241  }
242 
243  if (lCompletion.size() > Tokens.size()) {
244  if (lCompletion [Tokens.size()] == "%file") {
245  continue;
246  }
247 
248  const char* lCompletionCharStr (lCompletion [Tokens.size()].c_str());
249  if (strncmp (text, lCompletionCharStr, Length) == 0) {
250  // Readline will free the allocated memory
251  const size_t lCompletionSize = strlen (lCompletionCharStr) + 1;
252  char* NewString (static_cast<char*> (malloc (lCompletionSize)));
253  strcpy (NewString, lCompletionCharStr);
254 
255  ++Iterator;
256 
257  return NewString;
258  }
259  }
260  }
261 
262  return NULL;
263  }
264 
265 
266  // See the description near the prototype
267  int KeyDispatcher (int Count, int Key ) {
268  std::map< Keymap, KeysBind >::iterator Set (Keymaps.find (rl_get_keymap()));
269  if (Set == Keymaps.end()) {
270  // Most probably it happens bacause the header was
271  // included into many compilation units and the
272  // keymap setting calls were made in different files.
273  // This is the problem of "global" data.
274  // The storage of all the registered keymaps is in anonymous
275  // namespace.
276  throw std::runtime_error ("Error selecting a keymap.");
277  }
278 
279  (Set->second)[Key] (Count, Key);
280  return 0;
281  }
282 
283  // See the description near the prototype
284  int StartupHook (void) {
285  if (KeymapWasSetup) {
286  rl_set_keymap (Earlykeymap);
287  }
288  return 0;
289  }
290 
291 } // Anonymous namespace
292 
293 
299 namespace swift {
300 
307  class SKeymap {
308  private:
309  // Readline keymap
310  Keymap keymap;
311 
312  public:
319  explicit SKeymap (bool PrintableBound = false) : keymap (NULL) {
320  if (PrintableBound == true) {
321  // Printable characters are bound
322  keymap = rl_make_keymap();
323 
324  } else {
325  // Empty keymap
326  keymap = rl_make_bare_keymap();
327  }
328 
329  if (keymap == NULL) {
330  throw std::runtime_error ("Cannot allocate keymap.");
331  }
332 
333  // Register a new keymap in the global list
334  Keymaps [keymap] = KeysBind();
335  }
336 
342  explicit SKeymap (Keymap Pattern) : keymap (rl_copy_keymap (Pattern)) {
343  if ( keymap == NULL ) {
344  throw std::runtime_error( "Cannot allocate keymap." );
345  }
346 
347  // Register a new keymap in the global list
348  Keymaps [keymap] = KeysBind();
349  }
350 
355  // Deregister the keymap
356  Keymaps.erase (keymap);
357  rl_discard_keymap (keymap);
358  }
359 
366  void Bind (int Key, KeyCallback Callback) {
367  Keymaps [keymap][Key] = Callback;
368 
369  if (rl_bind_key_in_map (Key, KeyDispatcher, keymap) != 0) {
370  // Remove from the map just bound key
371  Keymaps [keymap].erase (Key);
372  throw std::runtime_error ("Invalid key.");
373  }
374  }
375 
381  void Unbind (int Key) {
382  rl_unbind_key_in_map (Key, keymap);
383  Keymaps [keymap].erase (Key);
384  }
385 
386  // void Bind (const std::string& Sequence, boost::function<int (int, int)>);
387  // void Unbind (std::string& Sequence);
388 
389  public:
395  SKeymap (const SKeymap& rhs) {
396  if (this == &rhs) {
397  return;
398  }
399  keymap = rl_copy_keymap (rhs.keymap);
400  }
401 
407  SKeymap& operator= (const SKeymap& rhs) {
408  if (this == &rhs) {
409  return *this;
410  }
411  keymap = rl_copy_keymap (rhs.keymap);
412  return *this;
413  }
414 
415  friend class SReadline;
416  };
417 
424  class SReadline {
425  public:
431  SReadline (const size_t Limit = DefaultHistoryLimit) :
432  HistoryLimit (Limit), HistoryFileName (""),
433  OriginalCompletion (rl_attempted_completion_function) {
434  rl_startup_hook = StartupHook;
435  rl_attempted_completion_function = UserCompletion;
436  using_history();
437  }
438 
446  SReadline( const std::string & historyFileName,
447  const size_t Limit = DefaultHistoryLimit ) :
448  HistoryLimit( Limit ),
449  HistoryFileName( historyFileName ),
450  OriginalCompletion( rl_attempted_completion_function )
451  {
452  rl_startup_hook = StartupHook;
453  rl_attempted_completion_function = UserCompletion;
454  using_history();
455  LoadHistory( HistoryFileName );
456  }
457 
463  rl_attempted_completion_function = OriginalCompletion;
464  SaveHistory (HistoryFileName);
465  }
466 
473  std::string GetLine (const std::string& Prompt) {
474  bool Unused;
475  return GetLine (Prompt, Unused);
476  }
477 
486  template <typename Container>
487  std::string GetLine (const std::string& Prompt, Container& ReadTokens) {
488  bool Unused;
489  return GetLine (Prompt, ReadTokens, Unused);
490  }
491 
501  template <typename Container>
502  std::string GetLine (const std::string& Prompt, Container& ReadTokens,
503  bool& BreakOut) {
504  std::string Input (GetLine (Prompt, BreakOut));
505  SplitTokens (Input, ReadTokens);
506  return Input;
507  }
508 
509 
517  std::string GetLine (const std::string& Prompt, bool& BreakOut) {
518  BreakOut = true;
519 
520  char* ReadLine (readline (Prompt.c_str()));
521  if (ReadLine == NULL) {
522  return std::string();
523  }
524 
525  // It's OK
526  BreakOut = false;
527  std::string Input (ReadLine);
528  free (ReadLine); ReadLine = NULL;
529 
530  boost::algorithm::trim (Input);
531  if (Input.empty() == false) {
532  if (history_length == 0
533  || Input != history_list()[ history_length - 1 ]->line) {
534  add_history (Input.c_str());
535 
536  if (history_length >= static_cast<int> (HistoryLimit)) {
537  stifle_history (HistoryLimit);
538  }
539  }
540  }
541 
542  return Input;
543  }
544 
545 
551  template <typename ContainerType>
552  void GetHistory (ContainerType& Container) {
553  for (int k (0); k < history_length; ++k ) {
554  Container.push_back (history_list()[k]->line);
555  }
556  }
557 
564  bool SaveHistory (std::ostream& OS) {
565  if (!OS) {
566  return false;
567  }
568 
569  for (int k (0); k < history_length; ++k) {
570  OS << history_list()[ k ]->line << std::endl;
571  }
572  return true;
573  }
574 
581  bool SaveHistory (const std::string& FileName) {
582  if (FileName.empty() == true) {
583  return false;
584  }
585 
586  std::ofstream OS (FileName.c_str());
587  return SaveHistory (OS);
588  }
589 
594  void ClearHistory() {
595  clear_history();
596  }
597 
604  bool LoadHistory (std::istream& IS) {
605  if (!IS) {
606  return false;
607  }
608 
609  ClearHistory();
610  std::string OneLine;
611 
612  while (!getline (IS, OneLine).eof()) {
613  boost::algorithm::trim( OneLine );
614  if ((history_length == 0)
615  || OneLine != history_list()[history_length - 1]->line) {
616  add_history (OneLine.c_str());
617  }
618  }
619  stifle_history (HistoryLimit);
620  return true;
621  }
622 
629  bool LoadHistory (const std::string& FileName) {
630  if (FileName.empty() == true) {
631  return false;
632  }
633 
634  std::ifstream IS (FileName.c_str());
635  return LoadHistory (IS);
636  }
637 
657  template <typename ContainerType>
658  void RegisterCompletions (const ContainerType& Container) {
659  Completions.clear();
660  for (typename ContainerType::const_iterator k (Container.begin());
661  k != Container.end(); ++k) {
662  std::vector<std::string> OneLine;
663  const std::string& kStr = static_cast<std::string> (*k);
664 
665  SplitTokens (kStr, OneLine);
666  Completions.push_back (OneLine);
667  }
668  }
669 
675  void SetKeymap (SKeymap& NewKeymap) {
676  rl_set_keymap (NewKeymap.keymap);
677  KeymapWasSetup = true;
678  Earlykeymap = NewKeymap.keymap;
679  }
680 
681 
682  private:
683  // /////////////////////////// Attributes /////////////////////////
687  const size_t HistoryLimit;
688 
692  const std::string HistoryFileName;
693 
697  rl_completion_func_t* OriginalCompletion;
698  };
699 
700 }; // namespace swift
701 
702 #endif
703