Dynamixel  2.9.5
RoadNarrows Robotics Dynamixel Package
dynashell_readline.cxx
Go to the documentation of this file.
1 ////////////////////////////////////////////////////////////////////////////////
2 //
3 // Package: Dynamixel
4 //
5 // Program: dynashell
6 //
7 // File: dynashell_readline.cxx
8 //
9 /*! \file
10  *
11  * $LastChangedDate: 2015-01-12 10:56:06 -0700 (Mon, 12 Jan 2015) $
12  * $Rev: 3845 $
13  *
14  * \brief The Dynamixel Shell ReadLine Class.
15  *
16  * \author Robin Knight (robin.knight@roadnarrows.com)
17  *
18  * \copyright
19  * \h_copy 2011-2017. RoadNarrows LLC.\n
20  * http://www.roadnarrows.com\n
21  * All Rights Reserved
22  */
23 /*
24  * @EulaBegin@
25  *
26  * Unless otherwise stated explicitly, all materials contained are copyrighted
27  * and may not be used without RoadNarrows LLC's written consent,
28  * except as provided in these terms and conditions or in the copyright
29  * notice (documents and software) or other proprietary notice provided with
30  * the relevant materials.
31  *
32  * IN NO EVENT SHALL THE AUTHOR, ROADNARROWS LLC, OR ANY
33  * MEMBERS/EMPLOYEES/CONTRACTORS OF ROADNARROWS OR DISTRIBUTORS OF THIS SOFTWARE
34  * BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR
35  * CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
36  * DOCUMENTATION, EVEN IF THE AUTHORS OR ANY OF THE ABOVE PARTIES HAVE BEEN
37  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38  *
39  * THE AUTHORS AND ROADNARROWS LLC SPECIFICALLY DISCLAIM ANY WARRANTIES,
40  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
41  * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN
42  * "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE NO OBLIGATION TO
43  * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
44  *
45  * @EulaEnd@
46  */
47 ////////////////////////////////////////////////////////////////////////////////
48 
49 #include <sys/types.h>
50 #include <unistd.h>
51 #include <errno.h>
52 #include <string.h>
53 #include <stdlib.h>
54 #include <time.h>
55 
56 #include <iostream>
57 #include <string>
58 #include <vector>
59 
60 #ifdef HAVE_READLINE
61 #include <readline/readline.h>
62 #include <readline/history.h>
63 #endif // HAVE_READLINE
64 
65 #include "dynashell_regex.h"
66 #include "dynashell_readline.h"
67 
68 using namespace std;
69 
70 
71 // ----------------------------------------------------------------------------
72 // Class ReadLineEntry
73 // ----------------------------------------------------------------------------
74 
75 /*!
76  * \brief Default constructor.
77  */
79 {
80  m_nUid = ReadLine::NOT_REG;
81  m_fnAppGen = NULL;
82  m_pAppArg = NULL;
83 }
84 
85 /*!
86  * \brief Initialization constructor.
87  *
88  * \param strRegEx Regular expression applied to current readline buffer state.
89  * \param fnAppGen Application-specific generator function.
90  * \param pAppArg Optional application argument generator function.
91  */
92 ReadLineEntry::ReadLineEntry(const string &strRegEx,
93  ReadLineAppGenFunc_T fnAppGen,
94  void *pAppArg)
95 {
96  m_nUid = ReadLine::NOT_REG;
97  m_regex = strRegEx;
98  m_fnAppGen = fnAppGen;
99  m_pAppArg = pAppArg;
100 }
101 
102 /*!
103  * \brief Initialization constructor.
104  *
105  * \param sRegEx Regular expression applied to current readline buffer state.
106  * \param fnAppGen Application-specific generator function.
107  * \param pAppArg Optional application argument generator function.
108  */
109 ReadLineEntry::ReadLineEntry(const char *sRegEx,
110  ReadLineAppGenFunc_T fnAppGen,
111  void *pAppArg)
112 {
113  m_nUid = ReadLine::NOT_REG;
114  m_regex = sRegEx;
115  m_fnAppGen = fnAppGen;
116  m_pAppArg = pAppArg;
117 }
118 
119 /*!
120  * \brief Copy constructor.
121  *
122  * \param src ReadLineEntry to copy.
123  */
125 {
126  m_nUid = src.m_nUid;
127  m_regex = src.m_regex;
128  m_fnAppGen = src.m_fnAppGen;
129  m_pAppArg = src.m_pAppArg;
130 }
131 
132 /*!
133  * \brief Default destructor.
134  */
136 {
137 }
138 
139 
140 // ----------------------------------------------------------------------------
141 // Class ReadLine
142 // ----------------------------------------------------------------------------
143 
144 /*!
145  * \brief Initialization constructor.
146  */
147 ReadLine::ReadLine(const string &strName)
148 {
149  if( strName.length() > 0 )
150  {
151  m_sName = dupstr(strName.c_str());
152  }
153  else
154  {
155  m_sName = dupstr("none");
156  }
157 
158  m_nUidCounter = 0;
159 
160 #ifdef HAVE_READLINE
161  // Allow conditional parsing of the ~/.inputrc file.
162  rl_readline_name = m_sName;
163 
164  // Tell the completer that we want a crack first.
165  rl_attempted_completion_function = CompletionWrap;
166 #endif // HAVE_READLINE
167 
168  // limitation of readline library which does not provide context feedback
169  ReadLineThis = this;
170 }
171 
172 /*!
173  * \brief Initialization constructor.
174  */
175 ReadLine::ReadLine(const char *sName)
176 {
177  if( sName != NULL )
178  {
179  m_sName = dupstr(sName);
180  }
181  else
182  {
183  m_sName = dupstr("none");
184  }
185 
186  m_nUidCounter = 0;
187 
188 #ifdef HAVE_READLINE
189  // Allow conditional parsing of the ~/.inputrc file.
190  rl_readline_name = m_sName;
191 
192  // Tell the completer that we want a crack first.
193  rl_attempted_completion_function = CompletionWrap;
194 #endif // HAVE_READLINE
195 
196  // limitation of readline library which does not provide context feedback
197  ReadLineThis = this;
198 }
199 
200 /*!
201  * \brief Destructor
202  */
204 {
205  if( m_sName != NULL )
206  {
207  delete[] m_sName;
208  }
209 }
210 
211 /*!
212  * \brief Register application-specific tab-completion generator associated.
213  *
214  * \param sRegEx Regular expression applied to current readline buffer state.
215  * \param fnAppGen Application-specific generator function.
216  * \param pAppArg Optional application argument generator function.
217  *
218  * \return
219  * On successful registration, a unique id \h_ge 0 is returned.\n
220  * On regular expression evalution failure, the generator is not registered and
221  * ReadLine::NOT_REG is returned.
222  */
223 int ReadLine::RegisterGenerator(const char *sRegEx,
224  ReadLineAppGenFunc_T fnAppGen,
225  void *pAppArg)
226 {
227  string str;
228  ReadLineEntry entry(sRegEx, fnAppGen, pAppArg);
229 
230  if( entry.IsValid() )
231  {
232  entry.m_nUid = m_nUidCounter;
233  m_vecGenerators.push_back(entry);
234  //fprintf(stderr, "DBG: %d: regex(%s)\n", m_nUidCounter, sRegEx);
235  return m_nUidCounter++;
236  }
237  else
238  {
239  return NOT_REG;
240  }
241 }
242 
243 /*!
244  * \brief Unregister application-specific generator associated with path.
245  *
246  * \param strPath Unique path string.
247  */
249 {
250  VecAppEntry::iterator iter;
251 
252  for(iter = m_vecGenerators.begin();
253  iter != m_vecGenerators.begin();
254  ++iter)
255  {
256  if( iter->GetUid() == nUid )
257  {
258  m_vecGenerators.erase(iter);
259  return;
260  }
261  }
262 }
263 
264 /*!
265  * \brief Read one input line from the given input stream.
266  *
267  * The freadline functions will read one line of input from the given input
268  * file pointer using the prompt string to prompt the user. If the prompt is
269  * NULL or an empty string then no prompt is issued. The line returned is
270  * allocated, so the caller must free it when finished.
271  *
272  * The line returned has the final newline removed, so only the text of the
273  * line remains.
274  *
275  * \param fp File pointer to input steam.
276  * \param sPrompt Optional user prompt string.
277  *
278  * \return If no errors occurred and EOF is not encountered, an allocated,
279  * null-terminated line buffer is return. Else NULL is return.
280  */
281 char *ReadLine::fReadLine(FILE *fp, const char *sPrompt)
282 {
283  static size_t BufSize = 4096;
284 
285  char *bufLine;
286  size_t n;
287 
288  bufLine = new char[BufSize];
289 
290  if( sPrompt && *sPrompt )
291  {
292  fprintf(stdout, "%s", sPrompt);
293  fflush(stdout);
294  }
295 
296  if( fgets(bufLine, BufSize, fp) == NULL )
297  {
298  delete[] bufLine;
299  return NULL;
300  }
301 
302  bufLine[BufSize-1] = 0;
303  n = strlen(bufLine);
304  if( (n > 0) && (bufLine[n-1] == '\n') )
305  {
306  bufLine[n-1] = 0;
307  }
308 
309  return bufLine;
310 }
311 
312 /*!
313  * \brief Add line to history.
314  *
315  * The line is added if the line is not empty and does not match the last
316  * command.
317  *
318  * \param Line to add.
319  */
320 void ReadLine::AddToHistory(const char *sLine)
321 {
322 #ifdef HAVE_READLINE
323  string str = sLine;
324  HIST_ENTRY *pCurHist;
325 
326  str = strip(str);
327 
328  if( !str.empty() )
329  {
330  pCurHist = previous_history();
331 
332  if( (pCurHist == NULL) || strcmp(pCurHist->line, str.c_str()) )
333  {
334  add_history(sLine);
335  }
336  }
337 #endif // HAVE_READLINE
338 }
339 
340 /*!
341  * \brief Duplicate string.
342  *
343  * \param s Null-terminated string to dup.
344  *
345  * \return Duplicated, allocated char *.
346  */
347 char *ReadLine::dupstr(const char *s)
348 {
349  char *t = new char[strlen(s)+1];
350  strcpy(t, s);
351  return t;
352 }
353 
354 /*!
355  * \brief Strip string of leading and trailing white space.
356  *
357  * \param [in] str Input string to strip.
358  *
359  * \return Stripped string.
360  */
361 string ReadLine::strip(string &str)
362 {
363  string t = str;
364  int i;
365 
366  // strip leading blanks
367  for(i=0; i<t.length(); ++i)
368  {
369  if( !isspace((int)t[i]) )
370  {
371  break;
372  }
373  }
374 
375  if( i != 0 )
376  {
377  t = t.substr(i);
378  }
379 
380  // strip trailing blanks
381  for(i=t.length()-1; i>0; --i)
382  {
383  if( !isspace((int)t[i]) )
384  {
385  break;
386  }
387  }
388 
389  if( i != t.length()-1 )
390  {
391  t = t.substr(0, i+1);
392  }
393 
394  return t;
395 }
396 
397 /*!
398  * \brief Strip string of leading and trailing white space.
399  *
400  * A null character '\0' is inserted after the last non-white space character.
401  *
402  * \param [in,out] s Null-terminated char*.
403  *
404  * \return Pointer to first non-white space character in s or to null-terminator
405  * if no non-white space characters are found.
406  */
407 char *ReadLine::strip(char *s)
408 {
409  char *left, *right;
410 
411  for(left = s; isspace((int)*left); left++) ;
412 
413  if( *left == 0 )
414  {
415  return left;
416  }
417 
418  right = left + strlen(left) - 1;
419 
420  while( (right > left) && isspace((int)*right) )
421  {
422  right--;
423  }
424 
425  *++right = '\0';
426 
427  return left;
428 }
429 
430 /*!
431  * \brief Canonicalization of a string.
432  *
433  * \note c14n is an cute abbreviation where 14 represents the number of letters
434  * between the c and n.
435  *
436  * \param s Null-terminated string to canonicalize.
437  * \param uLen (Sub)length of string to canonicalize.
438  *
439  * \return Return string holding canonical form.
440  */
441 string ReadLine::c14n(const char *s, size_t uLen)
442 {
443  string str(s, uLen);
444  size_t pos;
445 
446  // strip leading and trailing whitespace
447  str = ReadLine::strip(str);
448 
449  // convert multiple whitespace sequences to a single blank
450  if( str.length() != 0 )
451  {
452  while( (pos = str.find(" ")) != str.npos )
453  {
454  str = str.replace(pos, 2, " ");
455  }
456  }
457 
458  //cout << " (" << str << ")" << endl;
459 
460  return str;
461 }
462 
463 /*!
464  * \brief Tokenize input.
465  *
466  * \param [in,out] s Input string to tokenize.
467  * \param [out] tokv Array of tokens (pointer to locations in s).
468  * \param tokmax Maximum number of tokens.
469  *
470  * \return Number of tokens.
471  */
472 int ReadLine::tokenize(char *s, char *tokv[], size_t tokmax)
473 {
474  int tokc = 0;
475 
476  while( tokc < (int)tokmax )
477  {
478  // find start of the next token
479  while( *s && isspace((int)*s) )
480  {
481  s++;
482  }
483 
484  // no more tokens
485  if( *s == 0 )
486  {
487  return tokc;
488  }
489 
490  // new token
491  tokv[tokc++] = s;
492 
493  // find end of the token
494  while( *s && !isspace((int)*s) )
495  {
496  s++;
497  }
498 
499  // end of the line
500  if( *s == 0 )
501  {
502  return tokc;
503  }
504 
505  // null terminate
506  *s++ = 0;
507  }
508 
509  return tokc;
510 }
511 
512 /*!
513  * \brief Count the words in the string.
514  *
515  * \param str String to count.
516  *
517  * \return Number of words.
518  */
519 int ReadLine::wc(const char *s)
520 {
521  int wc = 0;
522 
523  while( *s )
524  {
525  // find start of the next word
526  while( *s && isspace((int)*s) )
527  {
528  s++;
529  }
530 
531  // no more words
532  if( *s == 0 )
533  {
534  break;
535  }
536 
537  ++wc;
538 
539  // find end of the word
540  while( *s && !isspace((int)*s) )
541  {
542  s++;
543  }
544  }
545 
546  return wc;
547 }
548 
549 /*!
550  * \brief Command completion callback function wrapper.
551  *
552  * Attempt to complete on the contents of text. The start and end bound the
553  * region of rl_line_buffer that contains the word to complete. Text is
554  * the word to complete. We can use the entire contents of rl_line_buffer
555  * in case we want to do some simple parsing.
556  *
557  * \param sText Text string to complete as a command.
558  * \param nStart Start index of text string in line.
559  * \param nEnd End index of text string in line.
560  *
561  * \return Array of matches, NULL if none.
562  */
563 char **ReadLine::CompletionWrap(const char *sText, int nStart, int nEnd)
564 {
565  return ReadLineThis->Completion(sText, nStart, nEnd);
566 }
567 
568 /*!
569  * \brief Command completion callback function.
570  *
571  * \param sText Text string to complete as a command.
572  * \param nStart Start index of text string in line.
573  * \param nEnd End index of text string in line.
574  *
575  * \return Array of matches, NULL if none.
576  */
577 char **ReadLine::Completion(const char *sText, int nStart, int nEnd)
578 {
579 #ifdef HAVE_READLINE
580  MatchGenerator(nStart);
581 
582  return rl_completion_matches(sText, GeneratorWrap);
583 
584 #else
585  return NULL;
586 #endif // HAVE_READLINE
587 }
588 
589 /*!
590  * \brief Generator wrapper.
591  *
592  * Calls the matched, registered application-specific generator.
593  *
594  * \param sText Partial text string to complete.
595  * \param nState Generator state. If FIRST,then initialize any statics.
596  *
597  * \return If a first/next match is made, return allocated completed match.\n
598  * Otherwise return NULL.
599  */
600 
601 char *ReadLine::GeneratorWrap(const char *sText, int nState)
602 {
603  // no registered generator match current path
604  if( ReadLineThis->m_posMatched == ReadLineThis->m_vecGenerators.end() )
605  {
606  return NULL;
607  }
608 
609  // first time through
610  if( nState == ReadLine::FIRST )
611  {
612  ReadLineThis->m_uTextLen = strlen(sText);
613  }
614 
615  // call application-specific generator
616  return ReadLineThis->m_posMatched->m_fnAppGen(
617  ReadLineThis->m_posMatched->GetUid(),
618  sText,
619  ReadLineThis->m_uTextLen,
620  nState,
621  ReadLineThis->m_strContext.c_str(),
622  ReadLineThis->m_posMatched->m_pAppArg);
623 }
624 
625 /*!
626  * \brief Find application-specific generator associated with the first
627  * characters in the readline buffer.
628  *
629  * \param nLen Length in readline buffer.
630  */
632 {
633  m_posMatched = m_vecGenerators.end();
634 
635  m_strContext.clear();
636 
637 #ifdef HAVE_READLINE
638 
639  if( nLen > 0 )
640  {
641  m_strContext = ReadLine::c14n(rl_line_buffer, (size_t)nLen);
642  }
643  else
644  {
645  m_strContext = "";
646  }
647 
648  //fprintf(stderr, "\nDBG: matching '%s' ", m_strContext.c_str());
649 
650  for(m_posMatched = m_vecGenerators.begin();
651  m_posMatched != m_vecGenerators.end();
652  ++m_posMatched)
653  {
654  //fprintf(stderr, "DBG: %d: regex(%s) ",
655  // m_posMatched->GetUid(), m_posMatched->m_regex.GetRegEx());
656 
657  if( m_posMatched->m_regex.Match(m_strContext) )
658  {
659  //fprintf(stderr, "matched regex(%s)", m_posMatched->m_regex.GetRegEx());
660  break;
661  }
662  }
663  //fprintf(stderr, "\n");
664 #endif // HAVE_READLINE
665 }
666 
667 /*!
668  * \brief Pointer to the only instance of readline supported per application.
669  */
char ** Completion(const char *sText, int nStart, int nEnd)
Command completion callback function.
int RegisterGenerator(const string strRegEx, ReadLineAppGenFunc_T fnAppGen, void *pAppArg)
Register application-specific tab-completion generator associated.
RegEx m_regex
applicatin matching regular expression
int m_nUid
unique id
Definition: t.py:1
static string c14n(const string &str, size_t nLen)
Canonicalization of a string.
~ReadLineEntry()
Default destructor.
static char * fReadLine(FILE *fp, const char *sPrompt)
Read one input line from the given input stream.
ReadLineAppGenFunc_T m_fnAppGen
application-specific generator
static const int FIRST
first state
static char * dupstr(const string &str)
Duplicate string.
static int tokenize(char *s, char *tokv[], size_t tokmax)
Tokenize input.
static string strip(string &str)
Strip string of leading and trailing white space.
static char ** CompletionWrap(const char *sText, int nStart, int nEnd)
Command completion callback function wrapper.
static ReadLine * ReadLineThis
static pointer this single instance
virtual ~ReadLine()
Destructor.
char *(* ReadLineAppGenFunc_T)(int nUid, const char *sText, size_t uTextLen, int nState, const char *sContext, void *pAppArg)
Application-specific tab completion generator function type.
static int wc(const string &str)
Count the words in the string.
void AddToHistory(const char *sInput)
Add line to history.
ReadLine(const string &strName)
Initialization constructor.
ReadLineEntry()
Default constructor.
static const int NOT_REG
not registered return value
ReadLine class provides a c++ wrapper around the readline c library.
void MatchGenerator(int nEnd)
Find application-specific generator associated with the first characters in the readline buffer...
The Dynamixel Shell ReadLine Class.
void * m_pAppArg
application-specific argument
bool IsValid()
Check if entry is valid.
The Dynamixel Shell Regular Expression Class.
void UnregisterGenerator(int nUid)
Unregister application-specific generator associated with path.
static char * GeneratorWrap(const char *sText, int nState)
Generator wrapper.