+++ /dev/null
-////////////////////////////////////////////////////////////////////////////////
-//
-// Copyright 2006 - 2017, Paul Beckingham, Federico Hernandez.
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included
-// in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
-// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
-// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
-//
-// http://www.opensource.org/licenses/mit-license.php
-//
-////////////////////////////////////////////////////////////////////////////////
-
-#include <cmake.h>
-#include <shared.h>
-#include <utf8.h>
-#include <algorithm>
-#include <sstream>
-#include <iostream>
-#include <iomanip>
-#include <cctype>
-#include <strings.h>
-#include <unistd.h>
-#include <sys/select.h>
-#include <cerrno>
-#include <csignal>
-#include <cmath>
-#include <cstring>
-#include <sys/wait.h>
-#include <format.h>
-
-///////////////////////////////////////////////////////////////////////////////
-void wrapText (
- std::vector <std::string>& lines,
- const std::string& text,
- const int width,
- bool hyphenate)
-{
- std::string line;
- unsigned int offset = 0;
- while (extractLine (line, text, width, hyphenate, offset))
- lines.push_back (line);
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// Split in a separator. Two adjacent separators means empty token.
-std::vector <std::string> split (const std::string& input, const char delimiter)
-{
- std::vector <std::string> results;
- std::string::size_type start = 0;
- std::string::size_type i;
- while ((i = input.find (delimiter, start)) != std::string::npos)
- {
- results.push_back (input.substr (start, i - start));
- start = i + 1;
- }
-
- if (input.length ())
- results.push_back (input.substr (start));
-
- return results;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// Split on words. Adjacent separators collapsed.
-std::vector <std::string> split (const std::string& input)
-{
- static std::string delims = " \t\n\f\r";
- std::vector <std::string> results;
-
- std::string::size_type start = 0;
- std::string::size_type end;
- while ((start = input.find_first_not_of (delims, start)) != std::string::npos)
- {
- if ((end = input.find_first_of (delims, start)) != std::string::npos)
- {
- results.push_back (input.substr (start, end - start));
- start = end;
- }
- else
- {
- results.push_back (input.substr (start));
- start = std::string::npos;
- }
- }
-
- return results;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-std::string join (
- const std::string& separator,
- const std::vector<int>& items)
-{
- std::stringstream s;
- auto size = items.size ();
- for (unsigned int i = 0; i < size; ++i)
- {
- if (i)
- s << separator;
-
- s << items[i];
- }
-
- return s.str ();
-}
-
-////////////////////////////////////////////////////////////////////////////////
-std::string join (
- const std::string& separator,
- const std::vector<std::string>& items)
-{
- std::stringstream s;
- auto size = items.size ();
- for (unsigned int i = 0; i < size; ++i)
- {
- if (i)
- s << separator;
-
- s << items[i];
- }
-
- return s.str ();
-}
-
-////////////////////////////////////////////////////////////////////////////////
-std::string str_replace (
- const std::string &str,
- const std::string& search,
- const std::string& replacement)
-{
- std::string modified {str};
- std::string::size_type pos = 0;
- while ((pos = modified.find (search, pos)) != std::string::npos)
- {
- modified.replace (pos, search.length (), replacement);
- pos += replacement.length ();
- }
-
- return modified;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-std::string trim (const std::string& input, const std::string& edible)
-{
- auto start = input.find_first_not_of (edible);
- auto end = input.find_last_not_of (edible);
-
- if (start == std::string::npos)
- return "";
-
- if (end == std::string::npos)
- return input.substr (start);
-
- return input.substr (start, end - start + 1);
-}
-
-////////////////////////////////////////////////////////////////////////////////
-std::string ltrim (const std::string& input, const std::string& edible)
-{
- auto start = input.find_first_not_of (edible);
- if (start == std::string::npos)
- return "";
-
- return input.substr (start);
-}
-
-////////////////////////////////////////////////////////////////////////////////
-std::string rtrim (const std::string& input, const std::string& edible)
-{
- if (input.find_first_not_of (edible) == std::string::npos)
- return "";
-
- auto end = input.find_last_not_of (edible);
- if (end == std::string::npos)
- return input;
-
- return input.substr (0, end + 1);
-}
-
-////////////////////////////////////////////////////////////////////////////////
-int longestWord (const std::string& input)
-{
- int longest = 0;
- int length = 0;
- std::string::size_type i = 0;
- int character;
-
- while ((character = utf8_next_char (input, i)))
- {
- if (character == ' ')
- {
- if (length > longest)
- longest = length;
-
- length = 0;
- }
- else
- length += mk_wcwidth (character);
- }
-
- if (length > longest)
- longest = length;
-
- return longest;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-int longestLine (const std::string& input)
-{
- int longest = 0;
- int length = 0;
- std::string::size_type i = 0;
- int character;
-
- while ((character = utf8_next_char (input, i)))
- {
- if (character == '\n')
- {
- if (length > longest)
- longest = length;
-
- length = 0;
- }
- else
- length += mk_wcwidth (character);
- }
-
- if (length > longest)
- longest = length;
-
- return longest;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// Walk the input text looking for a break point. A break point is one of:
-// - EOS
-// - \n
-// - last space before 'length' characters
-// - last punctuation (, ; . :) before 'length' characters, even if not
-// followed by a space
-// - first 'length' characters
-//
-// text "one two three\n four"
-// bytes 0123456789012 3456789
-// characters 1234567890a23 4567890
-//
-// leading_ws
-// ws ^ ^ ^^
-// punct
-// break ^
-bool extractLine (
- std::string& line,
- const std::string& text,
- int width,
- bool hyphenate,
- unsigned int& offset)
-{
- // Terminate processing.
- // Note: bytes vs bytes.
- if (offset >= text.length ())
- return false;
-
- std::string::size_type last_last_bytes = offset;
- std::string::size_type last_bytes = offset;
- std::string::size_type bytes = offset;
- unsigned int last_ws = 0;
- int character;
- int char_width = 0;
- int line_width = 0;
- while (1)
- {
- last_last_bytes = last_bytes;
- last_bytes = bytes;
- character = utf8_next_char (text, bytes);
-
- if (character == 0 ||
- character == '\n')
- {
- line = text.substr (offset, last_bytes - offset);
- offset = bytes;
- break;
- }
- else if (character == ' ')
- last_ws = last_bytes;
-
- char_width = mk_wcwidth (character);
- if (line_width + char_width > width)
- {
- int last_last_character = text[last_last_bytes];
- int last_character = text[last_bytes];
-
- // [case 1] one| two --> last_last != 32, last == 32, ws == 0
- if (last_last_character != ' ' &&
- last_character == ' ')
- {
- line = text.substr (offset, last_bytes - offset);
- offset = last_bytes + 1;
- break;
- }
-
- // [case 2] one |two --> last_last == 32, last != 32, ws != 0
- else if (last_last_character == ' ' &&
- last_character != ' ' &&
- last_ws != 0)
- {
- line = text.substr (offset, last_bytes - offset - 1);
- offset = last_bytes;
- break;
- }
-
- else if (last_last_character != ' ' &&
- last_character != ' ')
- {
- // [case 3] one t|wo --> last_last != 32, last != 32, ws != 0
- if (last_ws != 0)
- {
- line = text.substr (offset, last_ws - offset);
- offset = last_ws + 1;
- break;
- }
- // [case 4] on|e two --> last_last != 32, last != 32, ws == 0
- else
- {
- if (hyphenate)
- {
- line = text.substr (offset, last_bytes - offset - 1) + '-';
- offset = last_last_bytes;
- }
- else
- {
- line = text.substr (offset, last_bytes - offset);
- offset = last_bytes;
- }
- }
-
- break;
- }
- }
-
- line_width += char_width;
- }
-
- return true;
-}
-/*
-
-TODO Resolve above against below, which is from Taskwarrior 2.6.0, and known to
- be wrong.
-////////////////////////////////////////////////////////////////////////////////
-// Break UTF8 text into chunks no more than width characters.
-bool extractLine (
- std::string& line,
- const std::string& text,
- int width,
- bool hyphenate,
- unsigned int& offset)
-{
- // Terminate processing.
- if (offset >= text.length ())
- return false;
-
- int line_length {0};
- int character {0};
- std::string::size_type lastWordEnd {std::string::npos};
- bool something {false};
- std::string::size_type cursor {offset};
- std::string::size_type prior_cursor {offset};
- while ((character = utf8_next_char (text, cursor)))
- {
- // Premature EOL.
- if (character == '\n')
- {
- line = text.substr (offset, line_length);
- offset = cursor;
- return true;
- }
-
- if (! Lexer::isWhitespace (character))
- {
- something = true;
- if (! text[cursor] || Lexer::isWhitespace (text[cursor]))
- lastWordEnd = prior_cursor;
- }
-
- line_length += mk_wcwidth (character);
-
- if (line_length >= width)
- {
- // Backtrack to previous word end.
- if (lastWordEnd != std::string::npos)
- {
- // Eat one WS after lastWordEnd.
- std::string::size_type lastBreak = lastWordEnd;
- utf8_next_char (text, lastBreak);
-
- // Position offset at following char.
- std::string::size_type nextStart = lastBreak;
- utf8_next_char (text, nextStart);
-
- line = text.substr (offset, lastBreak - offset);
- offset = nextStart;
- return true;
- }
-
- // No backtrack, possible hyphenation.
- else if (hyphenate)
- {
- line = text.substr (offset, prior_cursor - offset) + '-';
- offset = prior_cursor;
- return true;
- }
-
- // No hyphenation, just truncation.
- else
- {
- line = text.substr (offset, cursor - offset);
- offset = cursor;
- return true;
- }
- }
-
- // Hindsight.
- prior_cursor = cursor;
- }
-
- // Residual text.
- if (something)
- {
- line = text.substr (offset, cursor - offset);
- offset = cursor;
- return true;
- }
-
- return false;
-}
-*/
-
-////////////////////////////////////////////////////////////////////////////////
-bool compare (
- const std::string& left,
- const std::string& right,
- bool sensitive /*= true*/)
-{
- // Use strcasecmp if required.
- if (! sensitive)
- return strcasecmp (left.c_str (), right.c_str ()) == 0 ? true : false;
-
- // Otherwise, just use std::string::operator==.
- return left == right;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-bool closeEnough (
- const std::string& reference,
- const std::string& attempt,
- unsigned int minLength /* = 0 */)
-{
- // An exact match is accepted first.
- if (compare (reference, attempt, false))
- return true;
-
- // A partial match will suffice.
- if (attempt.length () < reference.length () &&
- attempt.length () >= minLength)
- return compare (reference.substr (0, attempt.length ()), attempt, false);
-
- return false;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-int matchLength (
- const std::string& left,
- const std::string& right)
-{
- int pos = 0;
- while (left[pos] &&
- right[pos] &&
- left[pos] == right[pos])
- ++pos;
-
- return pos;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-std::string::size_type find (
- const std::string& text,
- const std::string& pattern,
- bool sensitive)
-{
- return find (text, pattern, 0, sensitive);
-}
-
-////////////////////////////////////////////////////////////////////////////////
-std::string::size_type find (
- const std::string& text,
- const std::string& pattern,
- std::string::size_type begin,
- bool sensitive)
-{
- // Implement a sensitive find, which is really just a loop withing a loop,
- // comparing lower-case versions of each character in turn.
- if (!sensitive)
- {
- // Handle empty pattern.
- const char* p = pattern.c_str ();
- size_t len = pattern.length ();
- if (len == 0)
- return 0;
-
- // Handle bad begin.
- if (begin >= text.length ())
- return std::string::npos;
-
- // Evaluate these once, for performance reasons.
- const char* start = text.c_str ();
- const char* t = start + begin;
- const char* end = start + text.size ();
-
- for (; t <= end - len; ++t)
- {
- int diff = 0;
- for (size_t i = 0; i < len; ++i)
- if ((diff = tolower (t[i]) - tolower (p[i])))
- break;
-
- // diff == 0 means there was no break from the loop, which only occurs
- // when a difference is detected. Therefore, the loop terminated, and
- // diff is zero.
- if (diff == 0)
- return t - start;
- }
-
- return std::string::npos;
- }
-
- // Otherwise, just use std::string::find.
- return text.find (pattern, begin);
-}
-
-////////////////////////////////////////////////////////////////////////////////
-std::string lowerCase (const std::string& input)
-{
- std::string output {input};
- std::transform (output.begin (), output.end (), output.begin (), tolower);
- return output;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-std::string upperCase (const std::string& input)
-{
- std::string output {input};
- std::transform (output.begin (), output.end (), output.begin (), toupper);
- return output;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-std::string upperCaseFirst (const std::string& input)
-{
- std::string output {input};
- output[0] = toupper (output[0]);
- return output;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-int autoComplete (
- const std::string& partial,
- const std::vector<std::string>& list,
- std::vector<std::string>& matches,
- int minimum/* = 1*/)
-{
- matches.clear ();
-
- // Handle trivial case.
- unsigned int length = partial.length ();
- if (length)
- {
- for (auto& item : list)
- {
- // An exact match is a special case. Assume there is only one exact match
- // and return immediately.
- if (partial == item)
- {
- matches.clear ();
- matches.push_back (item);
- return 1;
- }
-
- // Maintain a list of partial matches.
- else if (length >= (unsigned) minimum &&
- length <= item.length () &&
- partial == item.substr (0, length))
- matches.push_back (item);
- }
- }
-
- return matches.size ();
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// Uses std::getline, because std::cin eats leading whitespace, and that means
-// that if a newline is entered, std::cin eats it and never returns from the
-// "std::cin >> answer;" line, but it does display the newline. This way, with
-// std::getline, the newline can be detected, and the prompt re-written.
-static void signal_handler (int s)
-{
- if (s == SIGINT)
- {
- std::cout << "\n\nInterrupted: No changes made.\n";
- exit (1);
- }
-}
-
-bool confirm (const std::string& question)
-{
- std::vector <std::string> options {"yes", "no"};
- std::vector <std::string> matches;
-
- signal (SIGINT, signal_handler);
-
- do
- {
- std::cout << question
- << " (yes/no) ";
-
- std::string answer {""};
- std::getline (std::cin, answer);
- answer = std::cin.eof () ? "no" : lowerCase (trim (answer));
-
- autoComplete (answer, options, matches, 1); // Hard-coded 1.
- }
- while (! std::cin.eof () && matches.size () != 1);
-
- signal (SIGINT, SIG_DFL);
- return matches.size () == 1 && matches[0] == "yes" ? true : false;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// Run a binary with args, capturing output.
-int execute (
- const std::string& executable,
- const std::vector <std::string>& args,
- const std::string& input,
- std::string& output)
-{
- pid_t pid;
- int pin[2], pout[2];
- fd_set rfds, wfds;
- struct timeval tv;
- int select_retval, read_retval, write_retval;
- char buf[16384];
- unsigned int written;
- const char* input_cstr = input.c_str ();
-
- if (signal (SIGPIPE, SIG_IGN) == SIG_ERR) // Handled locally with EPIPE.
- throw std::string (std::strerror (errno));
-
- if (pipe (pin) == -1)
- throw std::string (std::strerror (errno));
-
- if (pipe (pout) == -1)
- throw std::string (std::strerror (errno));
-
- if ((pid = fork ()) == -1)
- throw std::string (std::strerror (errno));
-
- if (pid == 0)
- {
- // This is only reached in the child
- close (pin[1]); // Close the write end of the input pipe.
- close (pout[0]); // Close the read end of the output pipe.
-
- // Parent writes to pin[1]. Set read end pin[0] as STDIN for child.
- if (dup2 (pin[0], STDIN_FILENO) == -1)
- throw std::string (std::strerror (errno));
- close (pin[0]);
-
- // Parent reads from pout[0]. Set write end pout[1] as STDOUT for child.
- if (dup2 (pout[1], STDOUT_FILENO) == -1)
- throw std::string (std::strerror (errno));
- close (pout[1]);
-
- // Add executable as argv[0] and NULL-terminate the array for execvp().
- char** argv = new char* [args.size () + 2];
- argv[0] = (char*) executable.c_str ();
- for (unsigned int i = 0; i < args.size (); ++i)
- argv[i+1] = (char*) args[i].c_str ();
-
- argv[args.size () + 1] = NULL;
- _exit (execvp (executable.c_str (), argv));
- }
-
- // This is only reached in the parent
- close (pin[0]); // Close the read end of the input pipe.
- close (pout[1]); // Close the write end of the output pipe.
-
- if (input.size () == 0)
- {
- // Nothing to send to the child, close the pipe early.
- close (pin[1]);
- }
-
- output = "";
- read_retval = -1;
- written = 0;
- while (read_retval != 0 || input.size () != written)
- {
- FD_ZERO (&rfds);
- if (read_retval != 0)
- FD_SET (pout[0], &rfds);
-
- FD_ZERO (&wfds);
- if (input.size () != written)
- FD_SET (pin[1], &wfds);
-
- // On Linux, tv may be overwritten by select(). Reset it each time.
- // NOTE: Timeout chosen arbitrarily - we don't time out execute() calls.
- // select() is run over and over again unless the child exits or closes
- // its pipes.
- tv.tv_sec = 5;
- tv.tv_usec = 0;
-
- select_retval = select (std::max (pout[0], pin[1]) + 1, &rfds, &wfds, NULL, &tv);
-
- if (select_retval == -1)
- throw std::string (std::strerror (errno));
-
- // Write data to child's STDIN
- if (FD_ISSET (pin[1], &wfds))
- {
- write_retval = write (pin[1], input_cstr + written, input.size () - written);
- if (write_retval == -1)
- {
- if (errno == EPIPE)
- {
- // Child died (or closed the pipe) before reading all input.
- // We don't really care; pretend we wrote it all.
- write_retval = input.size () - written;
- }
- else
- {
- throw std::string (std::strerror (errno));
- }
- }
- written += write_retval;
-
- if (written == input.size ())
- {
- // Let the child know that no more input is coming by closing the pipe.
- close (pin[1]);
- }
- }
-
- // Read data from child's STDOUT
- if (FD_ISSET (pout[0], &rfds))
- {
- read_retval = read (pout[0], &buf, sizeof (buf) - 1);
- if (read_retval == -1)
- throw std::string (std::strerror (errno));
-
- buf[read_retval] = '\0';
- output += buf;
- }
- }
-
- close (pout[0]); // Close the read end of the output pipe.
-
- int status = -1;
- if (wait (&status) == -1)
- throw std::string (std::strerror (errno));
-
- if (WIFEXITED (status))
- {
- status = WEXITSTATUS (status);
- }
- else
- {
- throw std::string ("Error: Could not get Hook exit status!");
- }
-
- if (signal (SIGPIPE, SIG_DFL) == SIG_ERR) // We're done, return to default.
- throw std::string (std::strerror (errno));
-
- return status;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-std::string osName ()
-{
-#if defined (DARWIN)
- return "Darwin";
-#elif defined (SOLARIS)
- return "Solaris";
-#elif defined (CYGWIN)
- return "Cygwin";
-#elif defined (HAIKU)
- return "Haiku";
-#elif defined (OPENBSD)
- return "OpenBSD";
-#elif defined (FREEBSD)
- return "FreeBSD";
-#elif defined (NETBSD)
- return "NetBSD";
-#elif defined (LINUX)
- return "Linux";
-#elif defined (KFREEBSD)
- return "GNU/kFreeBSD";
-#elif defined (GNUHURD)
- return "GNU/Hurd";
-#else
- return "<unknown>";
-#endif
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// 16.8 Predefined macro names [cpp.predefined]
-//
-// The following macro names shall be defined by the implementation:
-//
-// __cplusplus
-// The name __cplusplus is defined to the value 201402L when compiling a C++
-// translation unit.156
-//
-// ---
-// 156) It is intended that future versions of this standard will replace the
-// value of this macro with a greater value. Non-conforming compilers should
-// use a value with at most five decimal digits.
-std::string cppCompliance ()
-{
-#ifdef __cplusplus
- auto level = __cplusplus;
-
- if (level == 199711) return "C++98/03";
- else if (level == 201103) return "C++11";
- else if (level == 201402) return "C++14";
-
- // This is a hack. Replace with correct value on standard publication.
- else if (level > 201700) return "C++17";
-
- // Unknown, just show the value.
- else if (level > 99999) return format (__cplusplus);
-#endif
-
- // No C++.
- return "non-compliant";
-}
-
-////////////////////////////////////////////////////////////////////////////////