diff --git a/.gitignore b/.gitignore index e3186fa76..ad5684123 100644 --- a/.gitignore +++ b/.gitignore @@ -75,6 +75,8 @@ perl/Makefile.config /src/nix-copy-closure/nix-copy-closure +/src/error-demo/error-demo + /src/build-remote/build-remote # /tests/ diff --git a/Makefile b/Makefile index e3057c36c..f6b4a3101 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,8 @@ makefiles = \ misc/upstart/local.mk \ doc/manual/local.mk \ tests/local.mk \ - tests/plugins/local.mk + tests/plugins/local.mk \ + src/error-demo/local.mk -include Makefile.config diff --git a/src/error-demo/error-demo.cc b/src/error-demo/error-demo.cc new file mode 100644 index 000000000..a9ff6057c --- /dev/null +++ b/src/error-demo/error-demo.cc @@ -0,0 +1,66 @@ +#include "error.hh" +#include "nixexpr.hh" + +#include +#include + +int main() +{ + using namespace nix; + + // In each program where errors occur, this has to be set. + ErrorInfo::programName = std::optional("error-demo"); + + // Error in a program; no hint and no nix code. + printErrorInfo( + ErrorInfo { .level = elError, + .name = "name", + .description = "error description", + }); + + // Warning with name, description, and hint. + // The hintfmt function makes all the substituted text yellow. + printErrorInfo( + ErrorInfo { .level = elWarning, + .name = "name", + .description = "error description", + .hint = std::optional( + hintfmt("there was a %1%", "warning")), + }); + + + // Warning with nix file, line number, column, and the lines of + // code where a warning occurred. + SymbolTable testTable; + auto problem_file = testTable.create("myfile.nix"); + + printErrorInfo( + ErrorInfo{ + .level = elWarning, + .name = "warning name", + .description = "warning description", + .hint = hintfmt("this hint has %1% templated %2%!!", "yellow", "values"), + .nixCode = NixCode { + .errPos = Pos(problem_file, 40, 13), + .prevLineOfCode = std::nullopt, + .errLineOfCode = "this is the problem line of code", + .nextLineOfCode = std::nullopt + }}); + + // Error with previous and next lines of code. + printErrorInfo( + ErrorInfo{ + .level = elError, + .name = "error name", + .description = "error description", + .hint = hintfmt("this hint has %1% templated %2%!!", "yellow", "values"), + .nixCode = NixCode { + .errPos = Pos(problem_file, 40, 13), + .prevLineOfCode = std::optional("previous line of code"), + .errLineOfCode = "this is the problem line of code", + .nextLineOfCode = std::optional("next line of code"), + }}); + + + return 0; +} diff --git a/src/error-demo/local.mk b/src/error-demo/local.mk new file mode 100644 index 000000000..2c528490a --- /dev/null +++ b/src/error-demo/local.mk @@ -0,0 +1,12 @@ +programs += error-demo + +error-demo_DIR := $(d) + +error-demo_SOURCES := \ + $(wildcard $(d)/*.cc) \ + +error-demo_CXXFLAGS += -I src/libutil -I src/libexpr + +error-demo_LIBS = libutil libexpr + +error-demo_LDFLAGS = -pthread $(SODIUM_LIBS) $(EDITLINE_LIBS) $(BOOST_LDFLAGS) -lboost_context -lboost_thread -lboost_system diff --git a/src/libutil/ansicolor.hh b/src/libutil/ansicolor.hh new file mode 100644 index 000000000..390bd4d17 --- /dev/null +++ b/src/libutil/ansicolor.hh @@ -0,0 +1,13 @@ +#pragma once + +namespace nix +{ + /* Some ANSI escape sequences. */ + #define ANSI_NORMAL "\e[0m" + #define ANSI_BOLD "\e[1m" + #define ANSI_FAINT "\e[2m" + #define ANSI_RED "\e[31;1m" + #define ANSI_GREEN "\e[32;1m" + #define ANSI_YELLOW "\e[33;1m" + #define ANSI_BLUE "\e[34;1m" +} diff --git a/src/libutil/error.cc b/src/libutil/error.cc new file mode 100644 index 000000000..a5571d4ec --- /dev/null +++ b/src/libutil/error.cc @@ -0,0 +1,146 @@ +#include "error.hh" + +#include +#include + +namespace nix +{ + +std::optional ErrorInfo::programName = std::nullopt; + +std::ostream& operator<<(std::ostream &os, const hintformat &hf) +{ + return os << hf.str(); +} + +string showErrPos(const ErrPos &errPos) +{ + if (errPos.column > 0) { + return fmt("(%1%:%2%)", errPos.lineNumber, errPos.column); + } else { + return fmt("(%1%)", errPos.lineNumber); + }; +} + +void printCodeLines(const string &prefix, const NixCode &nixCode) +{ + // previous line of code. + if (nixCode.prevLineOfCode.has_value()) { + std::cout << fmt("%1% %|2$5d|| %3%", + prefix, + (nixCode.errPos.lineNumber - 1), + *nixCode.prevLineOfCode) + << std::endl; + } + + // line of code containing the error.%2$+5d% + std::cout << fmt("%1% %|2$5d|| %3%", + prefix, + (nixCode.errPos.lineNumber), + nixCode.errLineOfCode) + << std::endl; + + // error arrows for the column range. + if (nixCode.errPos.column > 0) { + int start = nixCode.errPos.column; + std::string spaces; + for (int i = 0; i < start; ++i) { + spaces.append(" "); + } + + std::string arrows("^"); + + std::cout << fmt("%1% |%2%" ANSI_RED "%3%" ANSI_NORMAL, + prefix, + spaces, + arrows) << std::endl; + } + + // next line of code. + if (nixCode.nextLineOfCode.has_value()) { + std::cout << fmt("%1% %|2$5d|| %3%", + prefix, + (nixCode.errPos.lineNumber + 1), + *nixCode.nextLineOfCode) + << std::endl; + } +} + +void printErrorInfo(const ErrorInfo &einfo) +{ + int errwidth = 80; + string prefix = " "; + + string levelString; + switch (einfo.level) { + case ErrLevel::elError: { + levelString = ANSI_RED; + levelString += "error:"; + levelString += ANSI_NORMAL; + break; + } + case ErrLevel::elWarning: { + levelString = ANSI_YELLOW; + levelString += "warning:"; + levelString += ANSI_NORMAL; + break; + } + default: { + levelString = fmt("invalid error level: %1%", einfo.level); + break; + } + } + + int ndl = prefix.length() + levelString.length() + 3 + einfo.name.length() + einfo.programName.value_or("").length(); + int dashwidth = ndl > (errwidth - 3) ? 3 : errwidth - ndl; + + string dashes; + for (int i = 0; i < dashwidth; ++i) + dashes.append("-"); + + // divider. + std::cout << fmt("%1%%2%" ANSI_BLUE " %3% %4% %5% %6%" ANSI_NORMAL, + prefix, + levelString, + "---", + einfo.name, + dashes, + einfo.programName.value_or("")) + << std::endl; + + // filename. + if (einfo.nixCode.has_value()) { + if (einfo.nixCode->errPos.nixFile != "") { + string eline = einfo.nixCode->errLineOfCode != "" + ? string(" ") + showErrPos(einfo.nixCode->errPos) + : ""; + + std::cout << fmt("%1%in file: " ANSI_BLUE "%2%%3%" ANSI_NORMAL, + prefix, + einfo.nixCode->errPos.nixFile, + eline) << std::endl; + std::cout << prefix << std::endl; + } else { + std::cout << fmt("%1%from command line argument", prefix) << std::endl; + std::cout << prefix << std::endl; + } + } + + // description + std::cout << prefix << einfo.description << std::endl; + std::cout << prefix << std::endl; + + // lines of code. + if (einfo.nixCode->errLineOfCode != "") { + printCodeLines(prefix, *einfo.nixCode); + std::cout << prefix << std::endl; + } + + // hint + if (einfo.hint.has_value()) { + std::cout << prefix << *einfo.hint << std::endl; + std::cout << prefix << std::endl; + } +} + +} diff --git a/src/libutil/error.hh b/src/libutil/error.hh new file mode 100644 index 000000000..f402b692e --- /dev/null +++ b/src/libutil/error.hh @@ -0,0 +1,121 @@ +#ifndef error_hh +#define error_hh + +#include "ansicolor.hh" +#include +#include +#include +#include "types.hh" + +namespace nix +{ + +typedef enum { + elWarning, + elError +} ErrLevel; + +struct ErrPos +{ + int lineNumber; + int column; + string nixFile; + + template + ErrPos& operator=(const P &pos) + { + lineNumber = pos.line; + column = pos.column; + nixFile = pos.file; + return *this; + } + + template + ErrPos(const P &p) + { + *this = p; + } +}; + +struct NixCode +{ + ErrPos errPos; + std::optional prevLineOfCode; + string errLineOfCode; + std::optional nextLineOfCode; +}; + +// ---------------------------------------------------------------- +// format function for hints. same as fmt, except templated values +// are always in yellow. + +template +struct yellowify +{ + yellowify(T &s) : value(s) {} + T &value; +}; + +template +std::ostream& operator<<(std::ostream &out, const yellowify &y) +{ + return out << ANSI_YELLOW << y.value << ANSI_NORMAL; +} + +class hintformat +{ +public: + hintformat(string format) :fmt(format) + { + fmt.exceptions(boost::io::all_error_bits ^ boost::io::too_many_args_bit); + } + template + hintformat& operator%(const T &value) + { + fmt % yellowify(value); + return *this; + } + + std::string str() const + { + return fmt.str(); + } + + template + friend class AddHint; +private: + format fmt; +}; + +std::ostream& operator<<(std::ostream &os, const hintformat &hf); + +template +inline hintformat hintfmt(const std::string & fs, const Args & ... args) +{ + hintformat f(fs); + formatHelper(f, args...); + return f; +} + +// ------------------------------------------------- +// ErrorInfo. +struct ErrorInfo +{ + ErrLevel level; + string name; + string description; + std::optional hint; + std::optional nixCode; + + static std::optional programName; +}; + +// -------------------------------------------------------- +// error printing + +// just to cout for now. +void printErrorInfo(const ErrorInfo &einfo); + +} + +#endif diff --git a/src/libutil/types.hh b/src/libutil/types.hh index a1ce7b372..250c9581d 100644 --- a/src/libutil/types.hh +++ b/src/libutil/types.hh @@ -41,7 +41,8 @@ struct FormatOrString { string s; FormatOrString(const string & s) : s(s) { }; - FormatOrString(const format & f) : s(f.str()) { }; + template + FormatOrString(const F & f) : s(f.str()) { }; FormatOrString(const char * s) : s(s) { }; }; @@ -51,12 +52,13 @@ struct FormatOrString ... a_n’. However, ‘fmt(s)’ is equivalent to ‘s’ (so no %-expansion takes place). */ -inline void formatHelper(boost::format & f) +template +inline void formatHelper(F & f) { } -template -inline void formatHelper(boost::format & f, const T & x, const Args & ... args) +template +inline void formatHelper(F & f, const T & x, const Args & ... args) { formatHelper(f % x, args...); } diff --git a/src/libutil/util.hh b/src/libutil/util.hh index 815b1f288..8e24ef968 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -2,6 +2,8 @@ #include "types.hh" #include "logging.hh" +#include "ansicolor.hh" +#include "error.hh" #include #include @@ -446,15 +448,6 @@ std::string shellEscape(const std::string & s); void ignoreException(); -/* Some ANSI escape sequences. */ -#define ANSI_NORMAL "\e[0m" -#define ANSI_BOLD "\e[1m" -#define ANSI_FAINT "\e[2m" -#define ANSI_RED "\e[31;1m" -#define ANSI_GREEN "\e[32;1m" -#define ANSI_YELLOW "\e[33;1m" -#define ANSI_BLUE "\e[34;1m" - /* Tree formatting. */ constexpr char treeConn[] = "├───";