Merge nix-repl repository
This commit is contained in:
		
						commit
						c31000bc93
					
				
					 1 changed files with 719 additions and 0 deletions
				
			
		
							
								
								
									
										719
									
								
								src/nix/repl.cc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										719
									
								
								src/nix/repl.cc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,719 @@ | |||
| #include <nix/config.h> | ||||
| 
 | ||||
| #include <iostream> | ||||
| #include <cstdlib> | ||||
| 
 | ||||
| #include <setjmp.h> | ||||
| 
 | ||||
| #include <readline/readline.h> | ||||
| #include <readline/history.h> | ||||
| 
 | ||||
| #include "shared.hh" | ||||
| #include "eval.hh" | ||||
| #include "eval-inline.hh" | ||||
| #include "store-api.hh" | ||||
| #include "common-opts.hh" | ||||
| #include "get-drvs.hh" | ||||
| #include "derivations.hh" | ||||
| #include "affinity.hh" | ||||
| #include "globals.hh" | ||||
| 
 | ||||
| using namespace std; | ||||
| using namespace nix; | ||||
| 
 | ||||
| #define ESC_RED "\033[31m" | ||||
| #define ESC_GRE "\033[32m" | ||||
| #define ESC_YEL "\033[33m" | ||||
| #define ESC_BLU "\033[34;1m" | ||||
| #define ESC_MAG "\033[35m" | ||||
| #define ESC_CYA "\033[36m" | ||||
| #define ESC_END "\033[0m" | ||||
| 
 | ||||
| string programId = "nix-repl"; | ||||
| const string historyFile = string(getenv("HOME")) + "/.nix-repl-history"; | ||||
| 
 | ||||
| struct NixRepl | ||||
| { | ||||
|     string curDir; | ||||
|     EvalState state; | ||||
| 
 | ||||
|     Strings loadedFiles; | ||||
| 
 | ||||
|     const static int envSize = 32768; | ||||
|     StaticEnv staticEnv; | ||||
|     Env * env; | ||||
|     int displ; | ||||
|     StringSet varNames; | ||||
| 
 | ||||
|     StringSet completions; | ||||
|     StringSet::iterator curCompletion; | ||||
| 
 | ||||
|     NixRepl(const Strings & searchPath, nix::ref<Store> store); | ||||
|     void mainLoop(const Strings & files); | ||||
|     void completePrefix(string prefix); | ||||
|     bool getLine(string & input, const char * prompt); | ||||
|     Path getDerivationPath(Value & v); | ||||
|     bool processLine(string line); | ||||
|     void loadFile(const Path & path); | ||||
|     void initEnv(); | ||||
|     void reloadFiles(); | ||||
|     void addAttrsToScope(Value & attrs); | ||||
|     void addVarToScope(const Symbol & name, Value & v); | ||||
|     Expr * parseString(string s); | ||||
|     void evalString(string s, Value & v); | ||||
| 
 | ||||
|     typedef set<Value *> ValuesSeen; | ||||
|     std::ostream &  printValue(std::ostream & str, Value & v, unsigned int maxDepth); | ||||
|     std::ostream &  printValue(std::ostream & str, Value & v, unsigned int maxDepth, ValuesSeen & seen); | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| void printHelp() | ||||
| { | ||||
|     cout << "Usage: nix-repl [--help] [--version] [-I path] paths...\n" | ||||
|          << "\n" | ||||
|          << "nix-repl is a simple read-eval-print loop (REPL) for the Nix package manager.\n" | ||||
|          << "\n" | ||||
|          << "Options:\n" | ||||
|          << "    --help\n" | ||||
|          << "        Prints out a summary of the command syntax and exits.\n" | ||||
|          << "\n" | ||||
|          << "    --version\n" | ||||
|          << "        Prints out the Nix version number on standard output and exits.\n" | ||||
|          << "\n" | ||||
|          << "    -I path\n" | ||||
|          << "        Add a path to the Nix expression search path. This option may be given\n" | ||||
|          << "        multiple times. See the NIX_PATH environment variable for information on\n" | ||||
|          << "        the semantics of the Nix search path. Paths added through -I take\n" | ||||
|          << "        precedence over NIX_PATH.\n" | ||||
|          << "\n" | ||||
|          << "    paths...\n" | ||||
|          << "        A list of paths to files containing Nix expressions which nix-repl will\n" | ||||
|          << "        load and add to its scope.\n" | ||||
|          << "\n" | ||||
|          << "        A path surrounded in < and > will be looked up in the Nix expression search\n" | ||||
|          << "        path, as in the Nix language itself.\n" | ||||
|          << "\n" | ||||
|          << "        If an element of paths starts with http:// or https://, it is interpreted\n" | ||||
|          << "        as the URL of a tarball that will be downloaded and unpacked to a temporary\n" | ||||
|          << "        location. The tarball must include a single top-level directory containing\n" | ||||
|          << "        at least a file named default.nix.\n" | ||||
|          << flush; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| string removeWhitespace(string s) | ||||
| { | ||||
|     s = chomp(s); | ||||
|     size_t n = s.find_first_not_of(" \n\r\t"); | ||||
|     if (n != string::npos) s = string(s, n); | ||||
|     return s; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| NixRepl::NixRepl(const Strings & searchPath, nix::ref<Store> store) | ||||
|     : state(searchPath, store) | ||||
|     , staticEnv(false, &state.staticBaseEnv) | ||||
| { | ||||
|     curDir = absPath("."); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void NixRepl::mainLoop(const Strings & files) | ||||
| { | ||||
|     string error = ANSI_RED "error:" ANSI_NORMAL " "; | ||||
|     std::cout << "Welcome to Nix version " << NIX_VERSION << ". Type :? for help." << std::endl << std::endl; | ||||
| 
 | ||||
|     for (auto & i : files) | ||||
|         loadedFiles.push_back(i); | ||||
| 
 | ||||
|     reloadFiles(); | ||||
|     if (!loadedFiles.empty()) std::cout << std::endl; | ||||
| 
 | ||||
|     // Allow nix-repl specific settings in .inputrc
 | ||||
|     rl_readline_name = "nix-repl"; | ||||
|     using_history(); | ||||
|     read_history(historyFile.c_str()); | ||||
| 
 | ||||
|     string input; | ||||
| 
 | ||||
|     while (true) { | ||||
|         // When continuing input from previous lines, don't print a prompt, just align to the same
 | ||||
|         // number of chars as the prompt.
 | ||||
|         const char * prompt = input.empty() ? "nix-repl> " : "          "; | ||||
|         if (!getLine(input, prompt)) { | ||||
|             std::cout << std::endl; | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         try { | ||||
|             if (!removeWhitespace(input).empty() && !processLine(input)) return; | ||||
|         } catch (ParseError & e) { | ||||
|             if (e.msg().find("unexpected $end") != std::string::npos) { | ||||
|                 // For parse errors on incomplete input, we continue waiting for the next line of
 | ||||
|                 // input without clearing the input so far.
 | ||||
|                 continue; | ||||
|             } else { | ||||
|               printMsg(lvlError, format(error + "%1%%2%") % (settings.showTrace ? e.prefix() : "") % e.msg()); | ||||
|             } | ||||
|         } catch (Error & e) { | ||||
|             printMsg(lvlError, format(error + "%1%%2%") % (settings.showTrace ? e.prefix() : "") % e.msg()); | ||||
|         } catch (Interrupted & e) { | ||||
|             printMsg(lvlError, format(error + "%1%%2%") % (settings.showTrace ? e.prefix() : "") % e.msg()); | ||||
|         } | ||||
| 
 | ||||
|         // We handled the current input fully, so we should clear it and read brand new input.
 | ||||
|         input.clear(); | ||||
|         std::cout << std::endl; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /* Apparently, the only way to get readline() to return on Ctrl-C
 | ||||
|    (SIGINT) is to use siglongjmp().  That's fucked up... */ | ||||
| static sigjmp_buf sigintJmpBuf; | ||||
| 
 | ||||
| 
 | ||||
| static void sigintHandler(int signo) | ||||
| { | ||||
|     siglongjmp(sigintJmpBuf, 1); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /* Oh, if only g++ had nested functions... */ | ||||
| NixRepl * curRepl; | ||||
| 
 | ||||
| char * completerThunk(const char * s, int state) | ||||
| { | ||||
|     string prefix(s); | ||||
| 
 | ||||
|     /* If the prefix has a slash in it, use readline's builtin filename
 | ||||
|        completer. */ | ||||
|     if (prefix.find('/') != string::npos) | ||||
|         return rl_filename_completion_function(s, state); | ||||
| 
 | ||||
|     /* Otherwise, return all symbols that start with the prefix. */ | ||||
|     if (state == 0) { | ||||
|         curRepl->completePrefix(s); | ||||
|         curRepl->curCompletion = curRepl->completions.begin(); | ||||
|     } | ||||
|     if (curRepl->curCompletion == curRepl->completions.end()) return 0; | ||||
|     return strdup((curRepl->curCompletion++)->c_str()); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| bool NixRepl::getLine(string & input, const char * prompt) | ||||
| { | ||||
|     struct sigaction act, old; | ||||
|     act.sa_handler = sigintHandler; | ||||
|     sigfillset(&act.sa_mask); | ||||
|     act.sa_flags = 0; | ||||
|     if (sigaction(SIGINT, &act, &old)) | ||||
|         throw SysError("installing handler for SIGINT"); | ||||
| 
 | ||||
|     if (sigsetjmp(sigintJmpBuf, 1)) { | ||||
|         input.clear(); | ||||
|     } else { | ||||
|         curRepl = this; | ||||
|         rl_completion_entry_function = completerThunk; | ||||
| 
 | ||||
|         char * s = readline(prompt); | ||||
|         if (!s) return false; | ||||
|         input.append(s); | ||||
|         input.push_back('\n'); | ||||
|         if (!removeWhitespace(s).empty()) { | ||||
|             add_history(s); | ||||
|             append_history(1, 0); | ||||
|         } | ||||
|         free(s); | ||||
|     } | ||||
| 
 | ||||
|     _isInterrupted = 0; | ||||
| 
 | ||||
|     if (sigaction(SIGINT, &old, 0)) | ||||
|         throw SysError("restoring handler for SIGINT"); | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void NixRepl::completePrefix(string prefix) | ||||
| { | ||||
|     completions.clear(); | ||||
| 
 | ||||
|     size_t dot = prefix.rfind('.'); | ||||
| 
 | ||||
|     if (dot == string::npos) { | ||||
|         /* This is a variable name; look it up in the current scope. */ | ||||
|         StringSet::iterator i = varNames.lower_bound(prefix); | ||||
|         while (i != varNames.end()) { | ||||
|             if (string(*i, 0, prefix.size()) != prefix) break; | ||||
|             completions.insert(*i); | ||||
|             i++; | ||||
|         } | ||||
|     } else { | ||||
|         try { | ||||
|             /* This is an expression that should evaluate to an
 | ||||
|                attribute set.  Evaluate it to get the names of the | ||||
|                attributes. */ | ||||
|             string expr(prefix, 0, dot); | ||||
|             string prefix2 = string(prefix, dot + 1); | ||||
| 
 | ||||
|             Expr * e = parseString(expr); | ||||
|             Value v; | ||||
|             e->eval(state, *env, v); | ||||
|             state.forceAttrs(v); | ||||
| 
 | ||||
|             for (auto & i : *v.attrs) { | ||||
|                 string name = i.name; | ||||
|                 if (string(name, 0, prefix2.size()) != prefix2) continue; | ||||
|                 completions.insert(expr + "." + name); | ||||
|             } | ||||
| 
 | ||||
|         } catch (ParseError & e) { | ||||
|             // Quietly ignore parse errors.
 | ||||
|         } catch (EvalError & e) { | ||||
|             // Quietly ignore evaluation errors.
 | ||||
|         } catch (UndefinedVarError & e) { | ||||
|             // Quietly ignore undefined variable errors.
 | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static int runProgram(const string & program, const Strings & args) | ||||
| { | ||||
|     std::vector<const char *> cargs; /* careful with c_str()! */ | ||||
|     cargs.push_back(program.c_str()); | ||||
|     for (Strings::const_iterator i = args.begin(); i != args.end(); ++i) | ||||
|         cargs.push_back(i->c_str()); | ||||
|     cargs.push_back(0); | ||||
| 
 | ||||
|     Pid pid; | ||||
|     pid = fork(); | ||||
|     if (pid == -1) throw SysError("forking"); | ||||
|     if (pid == 0) { | ||||
|         restoreAffinity(); | ||||
|         execvp(program.c_str(), (char * *) &cargs[0]); | ||||
|         _exit(1); | ||||
|     } | ||||
| 
 | ||||
|     return pid.wait(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| bool isVarName(const string & s) | ||||
| { | ||||
|     if (s.size() == 0) return false; | ||||
|     char c = s[0]; | ||||
|     if ((c >= '0' && c <= '9') || c == '-' || c == '\'') return false; | ||||
|     for (auto & i : s) | ||||
|         if (!((i >= 'a' && i <= 'z') || | ||||
|               (i >= 'A' && i <= 'Z') || | ||||
|               (i >= '0' && i <= '9') || | ||||
|               i == '_' || i == '-' || i == '\'')) | ||||
|             return false; | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| Path NixRepl::getDerivationPath(Value & v) { | ||||
|     DrvInfo drvInfo(state); | ||||
|     if (!getDerivation(state, v, drvInfo, false)) | ||||
|         throw Error("expression does not evaluate to a derivation, so I can't build it"); | ||||
|     Path drvPath = drvInfo.queryDrvPath(); | ||||
|     if (drvPath == "" || !state.store->isValidPath(drvPath)) | ||||
|         throw Error("expression did not evaluate to a valid derivation"); | ||||
|     return drvPath; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| bool NixRepl::processLine(string line) | ||||
| { | ||||
|     if (line == "") return true; | ||||
| 
 | ||||
|     string command, arg; | ||||
| 
 | ||||
|     if (line[0] == ':') { | ||||
|         size_t p = line.find_first_of(" \n\r\t"); | ||||
|         command = string(line, 0, p); | ||||
|         if (p != string::npos) arg = removeWhitespace(string(line, p)); | ||||
|     } else { | ||||
|         arg = line; | ||||
|     } | ||||
| 
 | ||||
|     if (command == ":?" || command == ":help") { | ||||
|         cout << "The following commands are available:\n" | ||||
|              << "\n" | ||||
|              << "  <expr>        Evaluate and print expression\n" | ||||
|              << "  <x> = <expr>  Bind expression to variable\n" | ||||
|              << "  :a <expr>     Add attributes from resulting set to scope\n" | ||||
|              << "  :b <expr>     Build derivation\n" | ||||
|              << "  :i <expr>     Build derivation, then install result into current profile\n" | ||||
|              << "  :l <path>     Load Nix expression and add it to scope\n" | ||||
|              << "  :p <expr>     Evaluate and print expression recursively\n" | ||||
|              << "  :q            Exit nix-repl\n" | ||||
|              << "  :r            Reload all files\n" | ||||
|              << "  :s <expr>     Build dependencies of derivation, then start nix-shell\n" | ||||
|              << "  :t <expr>     Describe result of evaluation\n" | ||||
|              << "  :u <expr>     Build derivation, then start nix-shell\n"; | ||||
|     } | ||||
| 
 | ||||
|     else if (command == ":a" || command == ":add") { | ||||
|         Value v; | ||||
|         evalString(arg, v); | ||||
|         addAttrsToScope(v); | ||||
|     } | ||||
| 
 | ||||
|     else if (command == ":l" || command == ":load") { | ||||
|         state.resetFileCache(); | ||||
|         loadFile(arg); | ||||
|     } | ||||
| 
 | ||||
|     else if (command == ":r" || command == ":reload") { | ||||
|         state.resetFileCache(); | ||||
|         reloadFiles(); | ||||
|     } | ||||
| 
 | ||||
|     else if (command == ":t") { | ||||
|         Value v; | ||||
|         evalString(arg, v); | ||||
|         std::cout << showType(v) << std::endl; | ||||
| 
 | ||||
|     } else if (command == ":u") { | ||||
|         Value v, f, result; | ||||
|         evalString(arg, v); | ||||
|         evalString("drv: (import <nixpkgs> {}).runCommand \"shell\" { buildInputs = [ drv ]; } \"\"", f); | ||||
|         state.callFunction(f, v, result, Pos()); | ||||
| 
 | ||||
|         Path drvPath = getDerivationPath(result); | ||||
|         runProgram("nix-shell", Strings{drvPath}); | ||||
|     } | ||||
| 
 | ||||
|     else if (command == ":b" || command == ":i" || command == ":s") { | ||||
|         Value v; | ||||
|         evalString(arg, v); | ||||
|         Path drvPath = getDerivationPath(v); | ||||
| 
 | ||||
|         if (command == ":b") { | ||||
|             /* We could do the build in this process using buildPaths(),
 | ||||
|                but doing it in a child makes it easier to recover from | ||||
|                problems / SIGINT. */ | ||||
|             if (runProgram("nix-store", Strings{"-r", drvPath}) == 0) { | ||||
|                 Derivation drv = readDerivation(drvPath); | ||||
|                 std::cout << std::endl << "this derivation produced the following outputs:" << std::endl; | ||||
|                 for (auto & i : drv.outputs) | ||||
|                     std::cout << format("  %1% -> %2%") % i.first % i.second.path << std::endl; | ||||
|             } | ||||
|         } else if (command == ":i") { | ||||
|             runProgram("nix-env", Strings{"-i", drvPath}); | ||||
|         } else { | ||||
|             runProgram("nix-shell", Strings{drvPath}); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     else if (command == ":p" || command == ":print") { | ||||
|         Value v; | ||||
|         evalString(arg, v); | ||||
|         printValue(std::cout, v, 1000000000) << std::endl; | ||||
|     } | ||||
| 
 | ||||
|     else if (command == ":q" || command == ":quit") | ||||
|         return false; | ||||
| 
 | ||||
|     else if (command != "") | ||||
|         throw Error(format("unknown command ‘%1%’") % command); | ||||
| 
 | ||||
|     else { | ||||
|         size_t p = line.find('='); | ||||
|         string name; | ||||
|         if (p != string::npos && | ||||
|             p < line.size() && | ||||
|             line[p + 1] != '=' && | ||||
|             isVarName(name = removeWhitespace(string(line, 0, p)))) | ||||
|         { | ||||
|             Expr * e = parseString(string(line, p + 1)); | ||||
|             Value & v(*state.allocValue()); | ||||
|             v.type = tThunk; | ||||
|             v.thunk.env = env; | ||||
|             v.thunk.expr = e; | ||||
|             addVarToScope(state.symbols.create(name), v); | ||||
|         } else { | ||||
|             Value v; | ||||
|             evalString(line, v); | ||||
|             printValue(std::cout, v, 1) << std::endl; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void NixRepl::loadFile(const Path & path) | ||||
| { | ||||
|     loadedFiles.remove(path); | ||||
|     loadedFiles.push_back(path); | ||||
|     Value v, v2; | ||||
|     state.evalFile(lookupFileArg(state, path), v); | ||||
|     Bindings & bindings(*state.allocBindings(0)); | ||||
|     state.autoCallFunction(bindings, v, v2); | ||||
|     addAttrsToScope(v2); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void NixRepl::initEnv() | ||||
| { | ||||
|     env = &state.allocEnv(envSize); | ||||
|     env->up = &state.baseEnv; | ||||
|     displ = 0; | ||||
|     staticEnv.vars.clear(); | ||||
| 
 | ||||
|     varNames.clear(); | ||||
|     for (auto & i : state.staticBaseEnv.vars) | ||||
|         varNames.insert(i.first); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void NixRepl::reloadFiles() | ||||
| { | ||||
|     initEnv(); | ||||
| 
 | ||||
|     Strings old = loadedFiles; | ||||
|     loadedFiles.clear(); | ||||
| 
 | ||||
|     bool first = true; | ||||
|     for (auto & i : old) { | ||||
|         if (!first) std::cout << std::endl; | ||||
|         first = false; | ||||
|         std::cout << format("Loading ‘%1%’...") % i << std::endl; | ||||
|         loadFile(i); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void NixRepl::addAttrsToScope(Value & attrs) | ||||
| { | ||||
|     state.forceAttrs(attrs); | ||||
|     for (auto & i : *attrs.attrs) | ||||
|         addVarToScope(i.name, *i.value); | ||||
|     std::cout << format("Added %1% variables.") % attrs.attrs->size() << std::endl; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void NixRepl::addVarToScope(const Symbol & name, Value & v) | ||||
| { | ||||
|     if (displ >= envSize) | ||||
|         throw Error("environment full; cannot add more variables"); | ||||
|     staticEnv.vars[name] = displ; | ||||
|     env->values[displ++] = &v; | ||||
|     varNames.insert((string) name); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| Expr * NixRepl::parseString(string s) | ||||
| { | ||||
|     Expr * e = state.parseExprFromString(s, curDir, staticEnv); | ||||
|     return e; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void NixRepl::evalString(string s, Value & v) | ||||
| { | ||||
|     Expr * e = parseString(s); | ||||
|     e->eval(state, *env, v); | ||||
|     state.forceValue(v); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int maxDepth) | ||||
| { | ||||
|     ValuesSeen seen; | ||||
|     return printValue(str, v, maxDepth, seen); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| std::ostream & printStringValue(std::ostream & str, const char * string) { | ||||
|     str << "\""; | ||||
|     for (const char * i = string; *i; i++) | ||||
|         if (*i == '\"' || *i == '\\') str << "\\" << *i; | ||||
|         else if (*i == '\n') str << "\\n"; | ||||
|         else if (*i == '\r') str << "\\r"; | ||||
|         else if (*i == '\t') str << "\\t"; | ||||
|         else str << *i; | ||||
|     str << "\""; | ||||
|     return str; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| // FIXME: lot of cut&paste from Nix's eval.cc.
 | ||||
| std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int maxDepth, ValuesSeen & seen) | ||||
| { | ||||
|     str.flush(); | ||||
|     checkInterrupt(); | ||||
| 
 | ||||
|     state.forceValue(v); | ||||
| 
 | ||||
|     switch (v.type) { | ||||
| 
 | ||||
|     case tInt: | ||||
|         str << ESC_CYA << v.integer << ESC_END; | ||||
|         break; | ||||
| 
 | ||||
|     case tBool: | ||||
|         str << ESC_CYA << (v.boolean ? "true" : "false") << ESC_END; | ||||
|         break; | ||||
| 
 | ||||
|     case tString: | ||||
|         str << ESC_YEL; | ||||
|         printStringValue(str, v.string.s); | ||||
|         str << ESC_END; | ||||
|         break; | ||||
| 
 | ||||
|     case tPath: | ||||
|         str << ESC_GRE << v.path << ESC_END; // !!! escaping?
 | ||||
|         break; | ||||
| 
 | ||||
|     case tNull: | ||||
|         str << ESC_CYA "null" ESC_END; | ||||
|         break; | ||||
| 
 | ||||
|     case tAttrs: { | ||||
|         seen.insert(&v); | ||||
| 
 | ||||
|         bool isDrv = state.isDerivation(v); | ||||
| 
 | ||||
|         if (isDrv) { | ||||
|             str << "«derivation "; | ||||
|             Bindings::iterator i = v.attrs->find(state.sDrvPath); | ||||
|             PathSet context; | ||||
|             Path drvPath = i != v.attrs->end() ? state.coerceToPath(*i->pos, *i->value, context) : "???"; | ||||
|             str << drvPath << "»"; | ||||
|         } | ||||
| 
 | ||||
|         else if (maxDepth > 0) { | ||||
|             str << "{ "; | ||||
| 
 | ||||
|             typedef std::map<string, Value *> Sorted; | ||||
|             Sorted sorted; | ||||
|             for (auto & i : *v.attrs) | ||||
|                 sorted[i.name] = i.value; | ||||
| 
 | ||||
|             /* If this is a derivation, then don't show the
 | ||||
|                self-references ("all", "out", etc.). */ | ||||
|             StringSet hidden; | ||||
|             if (isDrv) { | ||||
|                 hidden.insert("all"); | ||||
|                 Bindings::iterator i = v.attrs->find(state.sOutputs); | ||||
|                 if (i == v.attrs->end()) | ||||
|                     hidden.insert("out"); | ||||
|                 else { | ||||
|                     state.forceList(*i->value); | ||||
|                     for (unsigned int j = 0; j < i->value->listSize(); ++j) | ||||
|                         hidden.insert(state.forceStringNoCtx(*i->value->listElems()[j])); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             for (auto & i : sorted) { | ||||
|                 if (isVarName(i.first)) | ||||
|                     str << i.first; | ||||
|                 else | ||||
|                     printStringValue(str, i.first.c_str()); | ||||
|                 str << " = "; | ||||
|                 if (hidden.find(i.first) != hidden.end()) | ||||
|                     str << "«...»"; | ||||
|                 else if (seen.find(i.second) != seen.end()) | ||||
|                     str << "«repeated»"; | ||||
|                 else | ||||
|                     try { | ||||
|                         printValue(str, *i.second, maxDepth - 1, seen); | ||||
|                     } catch (AssertionError & e) { | ||||
|                         str << ESC_RED "«error: " << e.msg() << "»" ESC_END; | ||||
|                     } | ||||
|                 str << "; "; | ||||
|             } | ||||
| 
 | ||||
|             str << "}"; | ||||
|         } else | ||||
|             str << "{ ... }"; | ||||
| 
 | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     case tList1: | ||||
|     case tList2: | ||||
|     case tListN: | ||||
|         seen.insert(&v); | ||||
| 
 | ||||
|         str << "[ "; | ||||
|         if (maxDepth > 0) | ||||
|             for (unsigned int n = 0; n < v.listSize(); ++n) { | ||||
|                 if (seen.find(v.listElems()[n]) != seen.end()) | ||||
|                     str << "«repeated»"; | ||||
|                 else | ||||
|                     try { | ||||
|                         printValue(str, *v.listElems()[n], maxDepth - 1, seen); | ||||
|                     } catch (AssertionError & e) { | ||||
|                         str << ESC_RED "«error: " << e.msg() << "»" ESC_END; | ||||
|                     } | ||||
|                 str << " "; | ||||
|             } | ||||
|         else | ||||
|             str << "... "; | ||||
|         str << "]"; | ||||
|         break; | ||||
| 
 | ||||
|     case tLambda: { | ||||
|         std::ostringstream s; | ||||
|         s << v.lambda.fun->pos; | ||||
|         str << ESC_BLU "«lambda @ " << filterANSIEscapes(s.str()) << "»" ESC_END; | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     case tPrimOp: | ||||
|         str << ESC_MAG "«primop»" ESC_END; | ||||
|         break; | ||||
| 
 | ||||
|     case tPrimOpApp: | ||||
|         str << ESC_BLU "«primop-app»" ESC_END; | ||||
|         break; | ||||
| 
 | ||||
|     default: | ||||
|         str << ESC_RED "«unknown»" ESC_END; | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     return str; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| int main(int argc, char * * argv) | ||||
| { | ||||
|     return handleExceptions(argv[0], [&]() { | ||||
|         initNix(); | ||||
|         initGC(); | ||||
| 
 | ||||
|         Strings files, searchPath; | ||||
| 
 | ||||
|         parseCmdLine(argc, argv, [&](Strings::iterator & arg, const Strings::iterator & end) { | ||||
|             if (*arg == "--version") | ||||
|                 printVersion("nix-repl"); | ||||
|             else if (*arg == "--help") { | ||||
|                 printHelp(); | ||||
|                 // exit with 0 since user asked for help
 | ||||
|                 _exit(0); | ||||
|             } | ||||
|             else if (parseSearchPathArg(arg, end, searchPath)) | ||||
|                 ; | ||||
|             else if (*arg != "" && arg->at(0) == '-') | ||||
|                 return false; | ||||
|             else | ||||
|                 files.push_back(*arg); | ||||
|             return true; | ||||
|         }); | ||||
| 
 | ||||
|         NixRepl repl(searchPath, openStore()); | ||||
|         repl.mainLoop(files); | ||||
| 
 | ||||
|         write_history(historyFile.c_str()); | ||||
|     }); | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue