appkit  1.5.1
RoadNarrows Robotics Application Kit
ReadLine.cxx
Go to the documentation of this file.
1 ////////////////////////////////////////////////////////////////////////////////
2 //
3 // Package: RoadNarrows Robotics Application Tool Kit
4 //
5 // Link: https://github.com/roadnarrows-robotics/rnr-sdk
6 //
7 // Library: librnr_appkit
8 //
9 // File: ReadLine.cxx
10 //
11 /*! \file
12  *
13  * \brief The ReadLine wrapper class implementaion.
14  *
15  * ReadLine provides a wrapper around the readline command-line C library.
16  *
17  * \author Robin Knight (robin.knight@roadnarrows.com)
18  *
19  * \par Copyright
20  * \h_copy 2011-2017. RoadNarrows LLC.\n
21  * http://www.roadnarrows.com\n
22  * All Rights Reserved
23  *
24  * \par Licence:
25  * MIT
26  */
27 /*
28  * @EulaBegin@
29  *
30  * Permission is hereby granted, without written agreement and without
31  * license or royalty fees, to use, copy, modify, and distribute this
32  * software and its documentation for any purpose, provided that
33  * (1) The above copyright notice and the following two paragraphs
34  * appear in all copies of the source code and (2) redistributions
35  * including binaries reproduces these notices in the supporting
36  * documentation. Substantial modifications to this software may be
37  * copyrighted by their authors and need not follow the licensing terms
38  * described here, provided that the new terms are clearly indicated in
39  * all files where they apply.
40  *
41  * IN NO EVENT SHALL THE AUTHOR, ROADNARROWS LLC, OR ANY MEMBERS/EMPLOYEES
42  * OF ROADNARROW LLC OR DISTRIBUTORS OF THIS SOFTWARE BE LIABLE TO ANY
43  * PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL
44  * DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION,
45  * EVEN IF THE AUTHORS OR ANY OF THE ABOVE PARTIES HAVE BEEN ADVISED OF
46  * THE POSSIBILITY OF SUCH DAMAGE.
47  *
48  * THE AUTHOR AND ROADNARROWS LLC SPECIFICALLY DISCLAIM ANY WARRANTIES,
49  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
50  * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN
51  * "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE NO OBLIGATION TO
52  * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
53  *
54  * @EulaEnd@
55  */
56 ////////////////////////////////////////////////////////////////////////////////
57 
58 #include <sys/types.h>
59 #include <unistd.h>
60 #include <errno.h>
61 #include <assert.h>
62 #include <string.h>
63 #include <stdlib.h>
64 #include <time.h>
65 
66 #include <sstream>
67 #include <iostream>
68 #include <string>
69 #include <vector>
70 #include <cctype>
71 #include <locale>
72 
73 #ifdef HAVE_READLINE
74 #include <readline/readline.h>
75 #include <readline/history.h>
76 #endif // HAVE_READLINE
77 
78 #include "rnr/rnrconfig.h"
79 #include "rnr/log.h"
80 
82 #include "rnr/appkit/ReadLine.h"
83 
84 using namespace std;
85 using namespace rnr;
86 using namespace rnr::str;
87 using namespace rnr::cmd;
88 
89 
90 // -----------------------------------------------------------------------------
91 // Private Implementation
92 // -----------------------------------------------------------------------------
93 
94 namespace rnr
95 {
96  namespace cmd
97  {
98  } // namespace cmd
99 } // namespace rnr
100 
101 
102 // ----------------------------------------------------------------------------
103 // Class ReadLine
104 // ----------------------------------------------------------------------------
105 
106 /*!
107  * \brief Pointer to the only instance of ReadLine supported per application.
108  */
109 ReadLine *ReadLine::ThisObj = NULL;
110 
111 ReadLine::ReadLine(const string strName,
112  const string strPrompt,
113  bool bUseRlLib) :
114  m_strName(strName), m_strPrompt(strPrompt), m_bUseRlLib(bUseRlLib)
115 {
116  m_uLineNum = 0;
117  m_bEoF = false;
118  m_bFError = false;
119  m_fnAppGen = NULL;
120  m_fnAltAppGen = NULL;
121  m_pAppArg = NULL;
122 
123 #ifdef HAVE_READLINE
124  //
125  // Only one instance of this class can be instantiated since:
126  // 1. The readline library provides no user argument(s) for callbacks to use.
127  // 2. And more importantly, readline library uses global variables.
128  //
129  assert(ThisObj == NULL);
130 
131  // limitation of readline library which does not provide context feedback
132  ThisObj = this;
133 
134  // tell readline completer that we want a crack at it first before default
135  rl_attempted_completion_function = ReadLine::completionWrapper;
136 
137  // allow conditional parsing of the ~/.inputrc file
138  rl_readline_name = m_strName.c_str();
139 
140 #endif // HAVE_READLINE
141 }
142 
144 {
145  ThisObj = NULL;
146 }
147 
148 void ReadLine::registerGenerator(AppGenFunc fnAppGen, void *pAppArg)
149 {
150  m_fnAppGen = fnAppGen;
151  m_fnAltAppGen = NULL;
152  m_pAppArg = pAppArg;
153 }
154 
155 void ReadLine::registerAltGenerator(AltAppGenFunc fnAltAppGen, void *pAppArg)
156 {
157  m_fnAppGen = NULL;
158  m_fnAltAppGen = fnAltAppGen;
159  m_pAppArg = pAppArg;
160 }
161 
163 {
164  m_fnAppGen = NULL;
165  m_fnAltAppGen = NULL;
166  m_pAppArg = NULL;
167 
168 #ifdef HAVE_READLINE
169  rl_attempted_completion_over = 0;
170  rl_completion_suppress_append = 0;
171 #endif // HAVE_READLINE
172 }
173 
175 {
176 #ifdef HAVE_READLINE
177  bool bInteractive = isInteractive(stdin);
178 
179  //
180  // Use readline library.
181  //
182  if( m_bUseRlLib && bInteractive )
183  {
185  m_strLine.clear();
186 
187  char *sLine = readline(getPrompt().c_str()); // fixed at stdin
188 
189  if( sLine != NULL )
190  {
191  m_strLine = sLine;
192  free(sLine);
193  ++m_uLineNum;
194  }
195 
196  else
197  {
198  setStreamStatus(stdin);
199  }
200 
201  return m_strLine;
202  }
203 
204  //
205  // Interactive, but don't use the readline library.
206  //
207  else if( bInteractive )
208  {
209  return ireadLine(stdin);
210  }
211 
212  //
213  // Non-interactive.
214  //
215  else
216  {
217  return freadLine(stdin);
218  }
219 
220 #else // !HAVE_READLINE
221 
222  return ireadLine(stdin);
223 
224 #endif // HAVE_READLINE
225 }
226 
227 string &ReadLine::ireadLine(FILE *fp)
228 {
229  // prompt
230  if( isInteractive(fp) && !getPrompt().empty() )
231  {
232  fprintf(stdout, "%s", getPrompt().c_str());
233  fflush(stdout);
234  }
235 
236  return freadLine(fp);
237 }
238 
239 string &ReadLine::freadLine(FILE *fp)
240 {
241  static size_t BufSize = 4096;
242 
243  char bufLine[BufSize];
244  size_t n;
245 
247  m_strLine.clear();
248 
249  if( fgets(bufLine, BufSize, fp) != NULL )
250  {
251  bufLine[BufSize-1] = 0;
252  n = strlen(bufLine);
253 
254  // some characters were read
255  if( n > 0 )
256  {
257  // got end of line
258  if( bufLine[n-1] == '\n' )
259  {
260  bufLine[n-1] = 0;
261  ++m_uLineNum;
262  }
263 
264  // oops, input line trunctated - too long
265  else
266  {
267  bufLine[n-1] = 0;
268  ++m_uLineNum;
269  LOGWARN("Input line too long. Exceeds %zu characters.", BufSize);
270  }
271  }
272 
273  // no i/o errors but also no characters read - odd
274  else
275  {
276  setStreamStatus(fp);
277 
278  bufLine[0] = 0;
279  }
280  }
281 
282  // read failure
283  else
284  {
285  setStreamStatus(fp);
286 
287  bufLine[0] = 0;
288  }
289 
290  m_strLine = bufLine;
291 
292  trim(m_strLine);
293 
294  return m_strLine;
295 }
296 
297 void ReadLine::addToHistory(const string &strLine)
298 {
299 #ifdef HAVE_READLINE
300  string str(strLine);
301  HIST_ENTRY *pCurHist;
302 
303  trim(str);
304 
305  if( !str.empty() )
306  {
307  pCurHist = previous_history();
308 
309  if( (pCurHist == NULL) || strcmp(pCurHist->line, str.c_str()) )
310  {
311  add_history(str.c_str());
312  }
313  }
314 #endif // HAVE_READLINE
315 }
316 
318 {
319  return isatty(fileno(fp))? true: false;
320 }
321 
322 const string &ReadLine::getErrorStr() const
323 {
324  return m_strError;
325 }
326 
328 {
329  m_bEoF = feof(fp)? true: false;
330  m_bFError = ferror(fp)? true: false;
331 
332  if( m_bFError )
333  {
334  stringstream ss;
335 
336  if( errno > 0 )
337  {
338  ss << strerror(errno) << "(errno=" << errno << "}";
339  }
340  else
341  {
342  ss << "File read error.";
343  }
344 
345  m_strError = ss.str();
346  }
347 }
348 
350 {
351  m_bEoF = false;
352  m_bFError = false;
353  m_strError.clear();
354 }
355 
356 char *ReadLine::dupstr(const char *s)
357 {
358  size_t size = sizeof(char) * (strlen(s) + 1);
359  char *t = (char *)malloc(size);
360  strcpy(t, s);
361  return t;
362 }
363 
364 size_t ReadLine::tokenize(const string &str, StringVec &tokens)
365 {
366  size_t i, len;
367  string tok;
368 
369  tokens.clear();
370 
371  len = str.length();
372 
373  for(i = 0; i < len; )
374  {
375  // find start of the next token
376  while( (i < len) && isspace((int)str[i]) )
377  {
378  ++i;
379  }
380 
381  // no more tokens
382  if( i >= len )
383  {
384  break;
385  }
386 
387  tok.clear();
388 
389  // find end of word
390  while( (i < len) && !isspace((int)str[i]) )
391  {
392  tok.push_back(str[i++]);
393  }
394 
395  tokens.push_back(tok);
396  }
397 
398  return tokens.size();
399 }
400 
401 size_t ReadLine::tokenize(char *s, char *tokv[], size_t tokmax)
402 {
403  int tokc = 0;
404 
405  while( tokc < (int)tokmax )
406  {
407  // find start of the next token
408  while( *s && isspace((int)*s) )
409  {
410  s++;
411  }
412 
413  // no more tokens
414  if( *s == 0 )
415  {
416  return tokc;
417  }
418 
419  // new token
420  tokv[tokc++] = s;
421 
422  // find end of the token
423  while( *s && !isspace((int)*s) )
424  {
425  s++;
426  }
427 
428  // end of the line
429  if( *s == 0 )
430  {
431  return tokc;
432  }
433 
434  // null terminate
435  *s++ = 0;
436  }
437 
438  return (size_t)tokc;
439 }
440 
441 int ReadLine::wc(const char *s)
442 {
443  int wc = 0;
444 
445  while( *s )
446  {
447  // find start of the next word
448  while( *s && isspace((int)*s) )
449  {
450  s++;
451  }
452 
453  // no more words
454  if( *s == 0 )
455  {
456  break;
457  }
458 
459  ++wc;
460 
461  // find end of the word
462  while( *s && !isspace((int)*s) )
463  {
464  s++;
465  }
466  }
467 
468  return wc;
469 }
470 
471 char **ReadLine::completionWrapper(const char *sText, int nStart, int nEnd)
472 {
473  return ThisObj->completion(sText, nStart, nEnd);
474 }
475 
476 char **ReadLine::completion(const char *sText, int nStart, int nEnd)
477 {
478 #ifdef HAVE_READLINE
479  if( m_fnAltAppGen != NULL )
480  {
481  return altCompletion(sText, nStart, nEnd);
482  }
483  else
484  {
485  return rl_completion_matches(sText, generatorWrapper);
486  }
487 
488 #else
489  return NULL;
490 #endif // HAVE_READLINE
491 }
492 
493 char **ReadLine::altCompletion(const string strText, int nStart, int nEnd)
494 {
495 #ifdef HAVE_READLINE
496  string strContext(rl_line_buffer);
497  int nIndex;
498  unsigned uFlags;
499  string strMatch;
500  vector<string> matchList;
501  size_t i;
502  char **tabList;
503 
504  rl_attempted_completion_over = 0; // disable default filename TAB completion
505  rl_completion_suppress_append = 0; // enable default space (' ') after match
506 
507  // no flags
508  uFlags = 0;
509 
510  for(nIndex = 0; ; ++nIndex)
511  {
512  strMatch = m_fnAltAppGen(m_pAppArg, strText, nIndex,
513  strContext, nStart, nEnd, uFlags);
514 
515  // end of completion generation
516  if( strMatch.empty() )
517  {
518  if( uFlags & FlagTabNoFilename )
519  {
520  rl_attempted_completion_over = 1;
521  }
522 
523  if( uFlags & FlagTabNoSpace )
524  {
525  rl_completion_suppress_append = 1;
526  }
527  break;
528  }
529 
530  // regular completion
531  else
532  {
533  // Empty string disables default. Undocumented.
534  if( (nIndex == 0) && (uFlags & FlagTabNoDefault) )
535  {
536  matchList.push_back("");
537  }
538  matchList.push_back(strMatch);
539  }
540  }
541 
542  // no matches
543  if( matchList.size() == 0 )
544  {
545  return NULL;
546  }
547 
548  //
549  // Malloc a tap-completion array.
550  //
551  // Note: The readlline library frees all entries in this array along with
552  // the array itself. Undocumented.
553  //
554  i = sizeof(char*) * (matchList.size() + 1);
555 
556  tabList = (char**)malloc(i);
557 
558  // copy allocated char *'s
559  for(i = 0; i < matchList.size(); ++i)
560  {
561  tabList[i] = dupstr(matchList[i]);
562  }
563  tabList[i] = NULL;
564 
565  return tabList;
566 
567 #else // !HAVE_READLINE
568 
569  return NULL;
570 
571 #endif // HAVE_READLINE
572 }
573 
574 char *ReadLine::generatorWrapper(const char *sText, int nState)
575 {
576 #ifdef HAVE_READLINE
577  // no registered generator match current path
578  if( ThisObj->m_fnAppGen == NULL )
579  {
580  return NULL;
581  }
582 
583  string strText(sText);
584  string strContext(rl_line_buffer);
585 
586  // trim left and right whitespace
587  trim(strContext);
588 
589  // first time through
590  if( nState == ReadLine::FIRST )
591  {
592  }
593 
594  // call application-specific generator
595  return ThisObj->m_fnAppGen(ThisObj->m_pAppArg, strText, nState, strContext);
596 
597 #else // !HAVE_READLINE
598 
599  return NULL;
600 
601 #endif // HAVE_READLINE
602 }
bool m_bFError
last file op file error condition
Definition: ReadLine.h:632
std::string m_strError
error string
Definition: ReadLine.h:633
std::string m_strName
readline name
Definition: ReadLine.h:626
no filename TAB completion attempt
Definition: ReadLine.h:124
std::vector< std::string > StringVec
Useful types.
Definition: StringTheory.h:83
std::string & rlreadLine()
Interactively read a line of input from standard input (stdin).
Definition: ReadLine.cxx:174
static char ** completionWrapper(const char *sText, int nStart, int nEnd)
Command completion callback function wrapper.
Definition: ReadLine.cxx:471
void clearStreamStatus()
Clear stream status prior to next read operation.
Definition: ReadLine.cxx:349
AppGenFunc m_fnAppGen
application-specific generator
Definition: ReadLine.h:634
char *(* AppGenFunc)(void *pAppArg, const std::string &strText, int nState, const std::string &strContext)
Application-specific TAB completion generator function type.
Definition: ReadLine.h:158
void registerAltGenerator(AltAppGenFunc fnAltAppGen, void *pAppArg)
Register alternate application-specific tab-completion generator.
Definition: ReadLine.cxx:155
String.
Definition: StringTheory.h:78
bool m_bUseRlLib
[do not] use readline library
Definition: ReadLine.h:628
no space(&#39; &#39;) after TAB completion
Definition: ReadLine.h:125
Of string spaces and their strangian operators.
const std::string & getPrompt() const
Get the current prompt string.
Definition: ReadLine.h:423
static int wc(const std::string &str)
Count the words in the string.
Definition: ReadLine.h:609
bool m_bEoF
last file op end of file condition
Definition: ReadLine.h:631
char ** altCompletion(const std::string strText, int nStart, int nEnd)
Command completion callback function.
Definition: ReadLine.cxx:493
static size_t tokenize(const std::string &str, str::StringVec &tokens)
Tokenize the given string.
std::string & trim(std::string &str)
Trim string in-place of leading and trailing whitespace.
Definition: StringTheory.h:197
static const int FIRST
first state
Definition: ReadLine.h:115
void * m_pAppArg
application-specific argument
Definition: ReadLine.h:636
void setStreamStatus(FILE *fp)
Set stream status for the last read operation.
Definition: ReadLine.cxx:327
Commands.
Definition: CmdAddOns.h:81
std::string & freadLine(FILE *fp)
Read one input line from the given input stream.
Definition: ReadLine.cxx:239
static char * generatorWrapper(const char *sText, int nState)
Generator wrapper.
Definition: ReadLine.cxx:574
The thin ReadLine wrapper class interface.
bool isInteractive(FILE *fp)
Test if input file pointer is interactive.
Definition: ReadLine.cxx:317
virtual ~ReadLine()
Destructor.
Definition: ReadLine.cxx:143
char ** completion(const char *sText, int nStart, int nEnd)
Command completion callback function.
Definition: ReadLine.cxx:476
size_t m_uLineNum
line number
Definition: ReadLine.h:630
ReadLine class provides a C++ wrapper around the readline C library.
Definition: ReadLine.h:112
const std::string(* AltAppGenFunc)(void *pAppArg, const std::string &strText, int nIndex, const std::string &strContext, int nStart, int nEnd, unsigned &uFlags)
Alternative application-specific TAB completion generator function type.
Definition: ReadLine.h:211
no default TAB completion match
Definition: ReadLine.h:123
void registerGenerator(AppGenFunc fnAppGen, void *pAppArg)
Register application-specific tab-completion generator.
Definition: ReadLine.cxx:148
AltAppGenFunc m_fnAltAppGen
alternate app-specific generator
Definition: ReadLine.h:635
std::string & ireadLine()
Interactively read a line of input from standard input (stdin).
Definition: ReadLine.h:313
static ReadLine * ThisObj
static pointer to this single instance
Definition: ReadLine.h:624
static char * dupstr(const std::string &str)
Duplicate string.
Definition: ReadLine.h:556
void unregisterGenerator()
Unregister application-specific generator associated with path.
Definition: ReadLine.cxx:162
RoadNarrows Robotics.
Definition: Camera.h:74
std::string m_strLine
last read line
Definition: ReadLine.h:629
const std::string & getErrorStr() const
Get the most recently set error string.
Definition: ReadLine.cxx:322