]> git.armaanb.net Git - gen-shell.git/blob - src/libshared/src/shared.cpp
added install instructions
[gen-shell.git] / src / libshared / src / shared.cpp
1 ////////////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright 2006 - 2017, Paul Beckingham, Federico Hernandez.
4 //
5 // Permission is hereby granted, free of charge, to any person obtaining a copy
6 // of this software and associated documentation files (the "Software"), to deal
7 // in the Software without restriction, including without limitation the rights
8 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 // copies of the Software, and to permit persons to whom the Software is
10 // furnished to do so, subject to the following conditions:
11 //
12 // The above copyright notice and this permission notice shall be included
13 // in all copies or substantial portions of the Software.
14 //
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 // SOFTWARE.
22 //
23 // http://www.opensource.org/licenses/mit-license.php
24 //
25 ////////////////////////////////////////////////////////////////////////////////
26
27 #include <cmake.h>
28 #include <shared.h>
29 #include <utf8.h>
30 #include <algorithm>
31 #include <sstream>
32 #include <iostream>
33 #include <iomanip>
34 #include <cctype>
35 #include <strings.h>
36 #include <unistd.h>
37 #include <sys/select.h>
38 #include <cerrno>
39 #include <csignal>
40 #include <cmath>
41 #include <cstring>
42 #include <sys/wait.h>
43 #include <format.h>
44
45 ///////////////////////////////////////////////////////////////////////////////
46 void wrapText (
47   std::vector <std::string>& lines,
48   const std::string& text,
49   const int width,
50   bool hyphenate)
51 {
52   std::string line;
53   unsigned int offset = 0;
54   while (extractLine (line, text, width, hyphenate, offset))
55     lines.push_back (line);
56 }
57
58 ////////////////////////////////////////////////////////////////////////////////
59 // Split in a separator. Two adjacent separators means empty token.
60 std::vector <std::string> split (const std::string& input, const char delimiter)
61 {
62   std::vector <std::string> results;
63   std::string::size_type start = 0;
64   std::string::size_type i;
65   while ((i = input.find (delimiter, start)) != std::string::npos)
66   {
67     results.push_back (input.substr (start, i - start));
68     start = i + 1;
69   }
70
71   if (input.length ())
72     results.push_back (input.substr (start));
73
74   return results;
75 }
76
77 ////////////////////////////////////////////////////////////////////////////////
78 // Split on words. Adjacent separators collapsed.
79 std::vector <std::string> split (const std::string& input)
80 {
81   static std::string delims = " \t\n\f\r";
82   std::vector <std::string> results;
83
84   std::string::size_type start = 0;
85   std::string::size_type end;
86   while ((start = input.find_first_not_of (delims, start)) != std::string::npos)
87   {
88     if ((end = input.find_first_of (delims, start)) != std::string::npos)
89     {
90       results.push_back (input.substr (start, end - start));
91       start = end;
92     }
93     else
94     {
95       results.push_back (input.substr (start));
96       start = std::string::npos;
97     }
98   }
99
100   return results;
101 }
102
103 ////////////////////////////////////////////////////////////////////////////////
104 std::string join (
105   const std::string& separator,
106   const std::vector<int>& items)
107 {
108   std::stringstream s;
109   auto size = items.size ();
110   for (unsigned int i = 0; i < size; ++i)
111   {
112     if (i)
113       s << separator;
114
115     s << items[i];
116   }
117
118   return s.str ();
119 }
120
121 ////////////////////////////////////////////////////////////////////////////////
122 std::string join (
123   const std::string& separator,
124   const std::vector<std::string>& items)
125 {
126   std::stringstream s;
127   auto size = items.size ();
128   for (unsigned int i = 0; i < size; ++i)
129   {
130     if (i)
131       s << separator;
132
133     s << items[i];
134   }
135
136   return s.str ();
137 }
138
139 ////////////////////////////////////////////////////////////////////////////////
140 std::string str_replace (
141   const std::string &str,
142   const std::string& search,
143   const std::string& replacement)
144 {
145   std::string modified {str};
146   std::string::size_type pos = 0;
147   while ((pos = modified.find (search, pos)) != std::string::npos)
148   {
149     modified.replace (pos, search.length (), replacement);
150     pos += replacement.length ();
151   }
152
153   return modified;
154 }
155
156 ////////////////////////////////////////////////////////////////////////////////
157 std::string trim (const std::string& input, const std::string& edible)
158 {
159   auto start = input.find_first_not_of (edible);
160   auto end   = input.find_last_not_of  (edible);
161
162   if (start == std::string::npos)
163     return "";
164
165   if (end == std::string::npos)
166     return input.substr (start);
167
168   return input.substr (start, end - start + 1);
169 }
170
171 ////////////////////////////////////////////////////////////////////////////////
172 std::string ltrim (const std::string& input, const std::string& edible)
173 {
174   auto start = input.find_first_not_of (edible);
175   if (start == std::string::npos)
176     return "";
177
178   return input.substr (start);
179 }
180
181 ////////////////////////////////////////////////////////////////////////////////
182 std::string rtrim (const std::string& input, const std::string& edible)
183 {
184   if (input.find_first_not_of (edible) == std::string::npos)
185     return "";
186
187   auto end = input.find_last_not_of (edible);
188   if (end == std::string::npos)
189     return input;
190
191   return input.substr (0, end + 1);
192 }
193
194 ////////////////////////////////////////////////////////////////////////////////
195 int longestWord (const std::string& input)
196 {
197   int longest = 0;
198   int length = 0;
199   std::string::size_type i = 0;
200   int character;
201
202   while ((character = utf8_next_char (input, i)))
203   {
204     if (character == ' ')
205     {
206       if (length > longest)
207         longest = length;
208
209       length = 0;
210     }
211     else
212       length += mk_wcwidth (character);
213   }
214
215   if (length > longest)
216     longest = length;
217
218   return longest;
219 }
220
221 ////////////////////////////////////////////////////////////////////////////////
222 int longestLine (const std::string& input)
223 {
224   int longest = 0;
225   int length = 0;
226   std::string::size_type i = 0;
227   int character;
228
229   while ((character = utf8_next_char (input, i)))
230   {
231     if (character == '\n')
232     {
233       if (length > longest)
234         longest = length;
235
236       length = 0;
237     }
238     else
239       length += mk_wcwidth (character);
240   }
241
242   if (length > longest)
243     longest = length;
244
245   return longest;
246 }
247
248 ////////////////////////////////////////////////////////////////////////////////
249 // Walk the input text looking for a break point.  A break point is one of:
250 //   - EOS
251 //   - \n
252 //   - last space before 'length' characters
253 //   - last punctuation (, ; . :) before 'length' characters, even if not
254 //     followed by a space
255 //   - first 'length' characters
256 //
257 // text       "one two three\n  four"
258 // bytes       0123456789012 3456789
259 // characters  1234567890a23 4567890
260 //
261 // leading_ws
262 // ws             ^   ^       ^^
263 // punct
264 // break                     ^
265 bool extractLine (
266   std::string& line,
267   const std::string& text,
268   int width,
269   bool hyphenate,
270   unsigned int& offset)
271 {
272   // Terminate processing.
273   // Note: bytes vs bytes.
274   if (offset >= text.length ())
275     return false;
276
277   std::string::size_type last_last_bytes = offset;
278   std::string::size_type last_bytes = offset;
279   std::string::size_type bytes = offset;
280   unsigned int last_ws = 0;
281   int character;
282   int char_width = 0;
283   int line_width = 0;
284   while (1)
285   {
286     last_last_bytes = last_bytes;
287     last_bytes = bytes;
288     character = utf8_next_char (text, bytes);
289
290     if (character == 0 ||
291         character == '\n')
292     {
293       line = text.substr (offset, last_bytes - offset);
294       offset = bytes;
295       break;
296     }
297     else if (character == ' ')
298       last_ws = last_bytes;
299
300     char_width = mk_wcwidth (character);
301     if (line_width + char_width > width)
302     {
303       int last_last_character = text[last_last_bytes];
304       int last_character = text[last_bytes];
305
306       // [case 1] one| two --> last_last != 32, last == 32, ws == 0
307       if (last_last_character != ' ' &&
308           last_character      == ' ')
309       {
310         line = text.substr (offset, last_bytes - offset);
311         offset = last_bytes + 1;
312         break;
313       }
314
315       // [case 2] one |two --> last_last == 32, last != 32, ws != 0
316       else if (last_last_character == ' ' &&
317                last_character      != ' ' &&
318                last_ws             != 0)
319       {
320         line = text.substr (offset, last_bytes - offset - 1);
321         offset = last_bytes;
322         break;
323       }
324
325       else if (last_last_character != ' ' &&
326                last_character      != ' ')
327       {
328         // [case 3] one t|wo --> last_last != 32, last != 32, ws != 0
329         if (last_ws != 0)
330         {
331           line = text.substr (offset, last_ws - offset);
332           offset = last_ws + 1;
333           break;
334         }
335         // [case 4] on|e two --> last_last != 32, last != 32, ws == 0
336         else
337         {
338           if (hyphenate)
339           {
340             line = text.substr (offset, last_bytes - offset - 1) + '-';
341             offset = last_last_bytes;
342           }
343           else
344           {
345             line = text.substr (offset, last_bytes - offset);
346             offset = last_bytes;
347           }
348         }
349
350         break;
351       }
352     }
353
354     line_width += char_width;
355   }
356
357   return true;
358 }
359 /*
360
361 TODO Resolve above against below, which is from Taskwarrior 2.6.0, and known to
362      be wrong.
363 ////////////////////////////////////////////////////////////////////////////////
364 // Break UTF8 text into chunks no more than width characters.
365 bool extractLine (
366   std::string& line,
367   const std::string& text,
368   int width,
369   bool hyphenate,
370   unsigned int& offset)
371 {
372   // Terminate processing.
373   if (offset >= text.length ())
374     return false;
375
376   int line_length                     {0};
377   int character                       {0};
378   std::string::size_type lastWordEnd  {std::string::npos};
379   bool something                      {false};
380   std::string::size_type cursor       {offset};
381   std::string::size_type prior_cursor {offset};
382   while ((character = utf8_next_char (text, cursor)))
383   {
384     // Premature EOL.
385     if (character == '\n')
386     {
387       line = text.substr (offset, line_length);
388       offset = cursor;
389       return true;
390     }
391
392     if (! Lexer::isWhitespace (character))
393     {
394       something = true;
395       if (! text[cursor] || Lexer::isWhitespace (text[cursor]))
396         lastWordEnd = prior_cursor;
397     }
398
399     line_length += mk_wcwidth (character);
400
401     if (line_length >= width)
402     {
403       // Backtrack to previous word end.
404       if (lastWordEnd != std::string::npos)
405       {
406         // Eat one WS after lastWordEnd.
407         std::string::size_type lastBreak = lastWordEnd;
408         utf8_next_char (text, lastBreak);
409
410         // Position offset at following char.
411         std::string::size_type nextStart = lastBreak;
412         utf8_next_char (text, nextStart);
413
414         line = text.substr (offset, lastBreak - offset);
415         offset = nextStart;
416         return true;
417       }
418
419       // No backtrack, possible hyphenation.
420       else if (hyphenate)
421       {
422         line = text.substr (offset, prior_cursor - offset) + '-';
423         offset = prior_cursor;
424         return true;
425       }
426
427       // No hyphenation, just truncation.
428       else
429       {
430         line = text.substr (offset, cursor - offset);
431         offset = cursor;
432         return true;
433       }
434     }
435
436     // Hindsight.
437     prior_cursor = cursor;
438   }
439
440   // Residual text.
441   if (something)
442   {
443     line = text.substr (offset, cursor - offset);
444      offset = cursor;
445     return true;
446   }
447
448   return false;
449 }
450 */
451
452 ////////////////////////////////////////////////////////////////////////////////
453 bool compare (
454   const std::string& left,
455   const std::string& right,
456   bool sensitive /*= true*/)
457 {
458   // Use strcasecmp if required.
459   if (! sensitive)
460     return strcasecmp (left.c_str (), right.c_str ()) == 0 ? true : false;
461
462   // Otherwise, just use std::string::operator==.
463   return left == right;
464 }
465
466 ////////////////////////////////////////////////////////////////////////////////
467 bool closeEnough (
468   const std::string& reference,
469   const std::string& attempt,
470   unsigned int minLength /* = 0 */)
471 {
472   // An exact match is accepted first.
473   if (compare (reference, attempt, false))
474     return true;
475
476   // A partial match will suffice.
477   if (attempt.length () < reference.length () &&
478       attempt.length () >= minLength)
479     return compare (reference.substr (0, attempt.length ()), attempt, false);
480
481   return false;
482 }
483
484 ////////////////////////////////////////////////////////////////////////////////
485 int matchLength (
486   const std::string& left,
487   const std::string& right)
488 {
489   int pos = 0;
490   while (left[pos] &&
491          right[pos] &&
492          left[pos] == right[pos])
493     ++pos;
494
495   return pos;
496 }
497
498 ////////////////////////////////////////////////////////////////////////////////
499 std::string::size_type find (
500   const std::string& text,
501   const std::string& pattern,
502   bool sensitive)
503 {
504   return find (text, pattern, 0, sensitive);
505 }
506
507 ////////////////////////////////////////////////////////////////////////////////
508 std::string::size_type find (
509   const std::string& text,
510   const std::string& pattern,
511   std::string::size_type begin,
512   bool sensitive)
513 {
514   // Implement a sensitive find, which is really just a loop withing a loop,
515   // comparing lower-case versions of each character in turn.
516   if (!sensitive)
517   {
518     // Handle empty pattern.
519     const char* p = pattern.c_str ();
520     size_t len = pattern.length ();
521     if (len == 0)
522       return 0;
523
524     // Handle bad begin.
525     if (begin >= text.length ())
526       return std::string::npos;
527
528     // Evaluate these once, for performance reasons.
529     const char* start = text.c_str ();
530     const char* t = start + begin;
531     const char* end = start + text.size ();
532
533     for (; t <= end - len; ++t)
534     {
535       int diff = 0;
536       for (size_t i = 0; i < len; ++i)
537         if ((diff = tolower (t[i]) - tolower (p[i])))
538           break;
539
540       // diff == 0 means there was no break from the loop, which only occurs
541       // when a difference is detected.  Therefore, the loop terminated, and
542       // diff is zero.
543       if (diff == 0)
544         return t - start;
545     }
546
547     return std::string::npos;
548   }
549
550   // Otherwise, just use std::string::find.
551   return text.find (pattern, begin);
552 }
553
554 ////////////////////////////////////////////////////////////////////////////////
555 std::string lowerCase (const std::string& input)
556 {
557   std::string output {input};
558   std::transform (output.begin (), output.end (), output.begin (), tolower);
559   return output;
560 }
561
562 ////////////////////////////////////////////////////////////////////////////////
563 std::string upperCase (const std::string& input)
564 {
565   std::string output {input};
566   std::transform (output.begin (), output.end (), output.begin (), toupper);
567   return output;
568 }
569
570 ////////////////////////////////////////////////////////////////////////////////
571 std::string upperCaseFirst (const std::string& input)
572 {
573   std::string output {input};
574   output[0] = toupper (output[0]);
575   return output;
576 }
577
578 ////////////////////////////////////////////////////////////////////////////////
579 int autoComplete (
580   const std::string& partial,
581   const std::vector<std::string>& list,
582   std::vector<std::string>& matches,
583   int minimum/* = 1*/)
584 {
585   matches.clear ();
586
587   // Handle trivial case.
588   unsigned int length = partial.length ();
589   if (length)
590   {
591     for (auto& item : list)
592     {
593       // An exact match is a special case.  Assume there is only one exact match
594       // and return immediately.
595       if (partial == item)
596       {
597         matches.clear ();
598         matches.push_back (item);
599         return 1;
600       }
601
602       // Maintain a list of partial matches.
603       else if (length >= (unsigned) minimum &&
604                length <= item.length ()     &&
605                partial == item.substr (0, length))
606         matches.push_back (item);
607     }
608   }
609
610   return matches.size ();
611 }
612
613 ////////////////////////////////////////////////////////////////////////////////
614 // Uses std::getline, because std::cin eats leading whitespace, and that means
615 // that if a newline is entered, std::cin eats it and never returns from the
616 // "std::cin >> answer;" line, but it does display the newline.  This way, with
617 // std::getline, the newline can be detected, and the prompt re-written.
618 static void signal_handler (int s)
619 {
620   if (s == SIGINT)
621   {
622     std::cout << "\n\nInterrupted: No changes made.\n";
623     exit (1);
624   }
625 }
626
627 bool confirm (const std::string& question)
628 {
629   std::vector <std::string> options {"yes", "no"};
630   std::vector <std::string> matches;
631
632   signal (SIGINT, signal_handler);
633
634   do
635   {
636     std::cout << question
637               << " (yes/no) ";
638
639     std::string answer {""};
640     std::getline (std::cin, answer);
641     answer = std::cin.eof () ? "no" : lowerCase (trim (answer));
642
643     autoComplete (answer, options, matches, 1); // Hard-coded 1.
644   }
645   while (! std::cin.eof () && matches.size () != 1);
646
647   signal (SIGINT, SIG_DFL);
648   return matches.size () == 1 && matches[0] == "yes" ? true : false;
649 }
650
651 ////////////////////////////////////////////////////////////////////////////////
652 // Run a binary with args, capturing output.
653 int execute (
654   const std::string& executable,
655   const std::vector <std::string>& args,
656   const std::string& input,
657   std::string& output)
658 {
659   pid_t pid;
660   int pin[2], pout[2];
661   fd_set rfds, wfds;
662   struct timeval tv;
663   int select_retval, read_retval, write_retval;
664   char buf[16384];
665   unsigned int written;
666   const char* input_cstr = input.c_str ();
667
668   if (signal (SIGPIPE, SIG_IGN) == SIG_ERR) // Handled locally with EPIPE.
669     throw std::string (std::strerror (errno));
670
671   if (pipe (pin) == -1)
672     throw std::string (std::strerror (errno));
673
674   if (pipe (pout) == -1)
675     throw std::string (std::strerror (errno));
676
677   if ((pid = fork ()) == -1)
678     throw std::string (std::strerror (errno));
679
680   if (pid == 0)
681   {
682     // This is only reached in the child
683     close (pin[1]);   // Close the write end of the input pipe.
684     close (pout[0]);  // Close the read end of the output pipe.
685
686     // Parent writes to pin[1]. Set read end pin[0] as STDIN for child.
687     if (dup2 (pin[0], STDIN_FILENO) == -1)
688       throw std::string (std::strerror (errno));
689     close (pin[0]);
690
691     // Parent reads from pout[0]. Set write end pout[1] as STDOUT for child.
692     if (dup2 (pout[1], STDOUT_FILENO) == -1)
693       throw std::string (std::strerror (errno));
694     close (pout[1]);
695
696     // Add executable as argv[0] and NULL-terminate the array for execvp().
697     char** argv = new char* [args.size () + 2];
698     argv[0] = (char*) executable.c_str ();
699     for (unsigned int i = 0; i < args.size (); ++i)
700       argv[i+1] = (char*) args[i].c_str ();
701
702     argv[args.size () + 1] = NULL;
703     _exit (execvp (executable.c_str (), argv));
704   }
705
706   // This is only reached in the parent
707   close (pin[0]);   // Close the read end of the input pipe.
708   close (pout[1]);  // Close the write end of the output pipe.
709
710   if (input.size () == 0)
711   {
712     // Nothing to send to the child, close the pipe early.
713     close (pin[1]);
714   }
715
716   output = "";
717   read_retval = -1;
718   written = 0;
719   while (read_retval != 0 || input.size () != written)
720   {
721     FD_ZERO (&rfds);
722     if (read_retval != 0)
723       FD_SET (pout[0], &rfds);
724
725     FD_ZERO (&wfds);
726     if (input.size () != written)
727       FD_SET (pin[1], &wfds);
728
729     // On Linux, tv may be overwritten by select().  Reset it each time.
730     // NOTE: Timeout chosen arbitrarily - we don't time out execute() calls.
731     // select() is run over and over again unless the child exits or closes
732     // its pipes.
733     tv.tv_sec = 5;
734     tv.tv_usec = 0;
735
736     select_retval = select (std::max (pout[0], pin[1]) + 1, &rfds, &wfds, NULL, &tv);
737
738     if (select_retval == -1)
739       throw std::string (std::strerror (errno));
740
741     // Write data to child's STDIN
742     if (FD_ISSET (pin[1], &wfds))
743     {
744       write_retval = write (pin[1], input_cstr + written, input.size () - written);
745       if (write_retval == -1)
746       {
747         if (errno == EPIPE)
748         {
749           // Child died (or closed the pipe) before reading all input.
750           // We don't really care; pretend we wrote it all.
751           write_retval = input.size () - written;
752         }
753         else
754         {
755           throw std::string (std::strerror (errno));
756         }
757       }
758       written += write_retval;
759
760       if (written == input.size ())
761       {
762         // Let the child know that no more input is coming by closing the pipe.
763         close (pin[1]);
764       }
765     }
766
767     // Read data from child's STDOUT
768     if (FD_ISSET (pout[0], &rfds))
769     {
770       read_retval = read (pout[0], &buf, sizeof (buf) - 1);
771       if (read_retval == -1)
772         throw std::string (std::strerror (errno));
773
774       buf[read_retval] = '\0';
775       output += buf;
776     }
777   }
778
779   close (pout[0]);  // Close the read end of the output pipe.
780
781   int status = -1;
782   if (wait (&status) == -1)
783     throw std::string (std::strerror (errno));
784
785   if (WIFEXITED (status))
786   {
787     status = WEXITSTATUS (status);
788   }
789   else
790   {
791     throw std::string ("Error: Could not get Hook exit status!");
792   }
793
794   if (signal (SIGPIPE, SIG_DFL) == SIG_ERR)  // We're done, return to default.
795     throw std::string (std::strerror (errno));
796
797   return status;
798 }
799
800 ////////////////////////////////////////////////////////////////////////////////
801 std::string osName ()
802 {
803 #if defined (DARWIN)
804   return "Darwin";
805 #elif defined (SOLARIS)
806   return "Solaris";
807 #elif defined (CYGWIN)
808   return "Cygwin";
809 #elif defined (HAIKU)
810   return "Haiku";
811 #elif defined (OPENBSD)
812   return "OpenBSD";
813 #elif defined (FREEBSD)
814   return "FreeBSD";
815 #elif defined (NETBSD)
816   return "NetBSD";
817 #elif defined (LINUX)
818   return "Linux";
819 #elif defined (KFREEBSD)
820   return "GNU/kFreeBSD";
821 #elif defined (GNUHURD)
822   return "GNU/Hurd";
823 #else
824   return "<unknown>";
825 #endif
826 }
827
828 ////////////////////////////////////////////////////////////////////////////////
829 // 16.8 Predefined macro names [cpp.predefined]
830 //
831 // The following macro names shall be defined by the implementation:
832 //
833 // __cplusplus
834 //   The name __cplusplus is defined to the value 201402L when compiling a C++
835 //   translation unit.156
836 //
837 // ---
838 //   156) It is intended that future versions of this standard will replace the
839 //   value of this macro with a greater value. Non-conforming compilers should
840 //   use a value with at most five decimal digits.
841 std::string cppCompliance ()
842 {
843 #ifdef __cplusplus
844   auto level = __cplusplus;
845
846        if (level == 199711) return "C++98/03";
847   else if (level == 201103) return "C++11";
848   else if (level == 201402) return "C++14";
849
850   // This is a hack.  Replace with correct value on standard publication.
851   else if (level >  201700) return "C++17";
852
853   // Unknown, just show the value.
854   else if (level >   99999) return format (__cplusplus);
855 #endif
856
857   // No C++.
858   return "non-compliant";
859 }
860
861 ////////////////////////////////////////////////////////////////////////////////