1*ec63e07aSXin Li--- jsonnet.cpp 2020-09-09 12:15:33.687539042 +0000 2*ec63e07aSXin Li+++ write_helper.cpp 2020-09-25 15:38:37.317147682 +0000 3*ec63e07aSXin Li@@ -14,559 +14,125 @@ 4*ec63e07aSXin Li limitations under the License. 5*ec63e07aSXin Li */ 6*ec63e07aSXin Li 7*ec63e07aSXin Li-#include <cassert> 8*ec63e07aSXin Li-#include <cstdlib> 9*ec63e07aSXin Li-#include <cstring> 10*ec63e07aSXin Li+// We need two functions defined in jsonnet.cpp file, used for writing output 11*ec63e07aSXin Li+// (multiple files and yaml streams) -- with minor changes (e.x. return type). 12*ec63e07aSXin Li 13*ec63e07aSXin Li-#include <exception> 14*ec63e07aSXin Li #include <fstream> 15*ec63e07aSXin Li #include <iostream> 16*ec63e07aSXin Li-#include <list> 17*ec63e07aSXin Li #include <map> 18*ec63e07aSXin Li-#include <sstream> 19*ec63e07aSXin Li-#include <string> 20*ec63e07aSXin Li #include <vector> 21*ec63e07aSXin Li 22*ec63e07aSXin Li-#include "utils.h" 23*ec63e07aSXin Li+#include "contrib/jsonnet/jsonnet_helper.h" 24*ec63e07aSXin Li 25*ec63e07aSXin Li-extern "C" { 26*ec63e07aSXin Li-#include <libjsonnet.h> 27*ec63e07aSXin Li-} 28*ec63e07aSXin Li- 29*ec63e07aSXin Li-#ifdef _WIN32 30*ec63e07aSXin Li-const char PATH_SEP = ';'; 31*ec63e07aSXin Li-#else 32*ec63e07aSXin Li-const char PATH_SEP = ':'; 33*ec63e07aSXin Li-#endif 34*ec63e07aSXin Li- 35*ec63e07aSXin Li-void version(std::ostream &o) 36*ec63e07aSXin Li-{ 37*ec63e07aSXin Li- o << "Jsonnet commandline interpreter " << jsonnet_version() << std::endl; 38*ec63e07aSXin Li-} 39*ec63e07aSXin Li- 40*ec63e07aSXin Li-void usage(std::ostream &o) 41*ec63e07aSXin Li-{ 42*ec63e07aSXin Li- version(o); 43*ec63e07aSXin Li- o << "\n"; 44*ec63e07aSXin Li- o << "jsonnet {<option>} <filename>\n"; 45*ec63e07aSXin Li- o << "\n"; 46*ec63e07aSXin Li- o << "Available options:\n"; 47*ec63e07aSXin Li- o << " -h / --help This message\n"; 48*ec63e07aSXin Li- o << " -e / --exec Treat filename as code\n"; 49*ec63e07aSXin Li- o << " -J / --jpath <dir> Specify an additional library search dir (right-most wins)\n"; 50*ec63e07aSXin Li- o << " -o / --output-file <file> Write to the output file rather than stdout\n"; 51*ec63e07aSXin Li- o << " -m / --multi <dir> Write multiple files to the directory, list files on stdout\n"; 52*ec63e07aSXin Li- o << " -y / --yaml-stream Write output as a YAML stream of JSON documents\n"; 53*ec63e07aSXin Li- o << " -S / --string Expect a string, manifest as plain text\n"; 54*ec63e07aSXin Li- o << " -s / --max-stack <n> Number of allowed stack frames\n"; 55*ec63e07aSXin Li- o << " -t / --max-trace <n> Max length of stack trace before cropping\n"; 56*ec63e07aSXin Li- o << " --gc-min-objects <n> Do not run garbage collector until this many\n"; 57*ec63e07aSXin Li- o << " --gc-growth-trigger <n> Run garbage collector after this amount of object growth\n"; 58*ec63e07aSXin Li- o << " --version Print version\n"; 59*ec63e07aSXin Li- o << "Available options for specifying values of 'external' variables:\n"; 60*ec63e07aSXin Li- o << "Provide the value as a string:\n"; 61*ec63e07aSXin Li- o << " -V / --ext-str <var>[=<val>] If <val> is omitted, get from environment var <var>\n"; 62*ec63e07aSXin Li- o << " --ext-str-file <var>=<file> Read the string from the file\n"; 63*ec63e07aSXin Li- o << "Provide a value as Jsonnet code:\n"; 64*ec63e07aSXin Li- o << " --ext-code <var>[=<code>] If <code> is omitted, get from environment var <var>\n"; 65*ec63e07aSXin Li- o << " --ext-code-file <var>=<file> Read the code from the file\n"; 66*ec63e07aSXin Li- o << "Available options for specifying values of 'top-level arguments':\n"; 67*ec63e07aSXin Li- o << "Provide the value as a string:\n"; 68*ec63e07aSXin Li- o << " -A / --tla-str <var>[=<val>] If <val> is omitted, get from environment var <var>\n"; 69*ec63e07aSXin Li- o << " --tla-str-file <var>=<file> Read the string from the file\n"; 70*ec63e07aSXin Li- o << "Provide a value as Jsonnet code:\n"; 71*ec63e07aSXin Li- o << " --tla-code <var>[=<code>] If <code> is omitted, get from environment var <var>\n"; 72*ec63e07aSXin Li- o << " --tla-code-file <var>=<file> Read the code from the file\n"; 73*ec63e07aSXin Li- o << "Environment variables:\n"; 74*ec63e07aSXin Li- o << "JSONNET_PATH is a colon (semicolon on Windows) separated list of directories added\n"; 75*ec63e07aSXin Li- o << "in reverse order before the paths specified by --jpath (i.e. left-most wins)\n"; 76*ec63e07aSXin Li- o << "E.g. JSONNET_PATH=a:b jsonnet -J c -J d is equivalent to:\n"; 77*ec63e07aSXin Li- o << "JSONNET_PATH=d:c:a:b jsonnet\n"; 78*ec63e07aSXin Li- o << "jsonnet -J b -J a -J c -J d\n"; 79*ec63e07aSXin Li- o << "\n"; 80*ec63e07aSXin Li- o << "In all cases:\n"; 81*ec63e07aSXin Li- o << "<filename> can be - (stdin)\n"; 82*ec63e07aSXin Li- o << "Multichar options are expanded e.g. -abc becomes -a -b -c.\n"; 83*ec63e07aSXin Li- o << "The -- option suppresses option processing for subsequent arguments.\n"; 84*ec63e07aSXin Li- o << "Note that since filenames and jsonnet programs can begin with -, it is advised to\n"; 85*ec63e07aSXin Li- o << "use -- if the argument is unknown, e.g. jsonnet -- \"$FILENAME\"."; 86*ec63e07aSXin Li- o << std::endl; 87*ec63e07aSXin Li-} 88*ec63e07aSXin Li- 89*ec63e07aSXin Li-/** Class for representing configuration read from command line flags. */ 90*ec63e07aSXin Li-struct JsonnetConfig { 91*ec63e07aSXin Li- std::vector<std::string> inputFiles; 92*ec63e07aSXin Li- std::string outputFile; 93*ec63e07aSXin Li- bool filenameIsCode; 94*ec63e07aSXin Li- 95*ec63e07aSXin Li- // EVAL flags 96*ec63e07aSXin Li- bool evalMulti; 97*ec63e07aSXin Li- bool evalStream; 98*ec63e07aSXin Li- std::string evalMultiOutputDir; 99*ec63e07aSXin Li- 100*ec63e07aSXin Li- JsonnetConfig() 101*ec63e07aSXin Li- : filenameIsCode(false), 102*ec63e07aSXin Li- evalMulti(false), 103*ec63e07aSXin Li- evalStream(false) 104*ec63e07aSXin Li- { 105*ec63e07aSXin Li- } 106*ec63e07aSXin Li-}; 107*ec63e07aSXin Li- 108*ec63e07aSXin Li-bool get_var_val(const std::string &var_val, std::string &var, std::string &val) 109*ec63e07aSXin Li-{ 110*ec63e07aSXin Li- size_t eq_pos = var_val.find_first_of('=', 0); 111*ec63e07aSXin Li- if (eq_pos == std::string::npos) { 112*ec63e07aSXin Li- var = var_val; 113*ec63e07aSXin Li- const char *val_cstr = ::getenv(var.c_str()); 114*ec63e07aSXin Li- if (val_cstr == nullptr) { 115*ec63e07aSXin Li- std::cerr << "ERROR: environment variable " << var << " was undefined." << std::endl; 116*ec63e07aSXin Li- return false; 117*ec63e07aSXin Li- } 118*ec63e07aSXin Li- val = val_cstr; 119*ec63e07aSXin Li- } else { 120*ec63e07aSXin Li- var = var_val.substr(0, eq_pos); 121*ec63e07aSXin Li- val = var_val.substr(eq_pos + 1, std::string::npos); 122*ec63e07aSXin Li- } 123*ec63e07aSXin Li- return true; 124*ec63e07aSXin Li-} 125*ec63e07aSXin Li- 126*ec63e07aSXin Li-bool get_var_file(const std::string &var_file, const std::string &imp, std::string &var, std::string &val) 127*ec63e07aSXin Li-{ 128*ec63e07aSXin Li- size_t eq_pos = var_file.find_first_of('=', 0); 129*ec63e07aSXin Li- if (eq_pos == std::string::npos) { 130*ec63e07aSXin Li- std::cerr << "ERROR: argument not in form <var>=<file> \"" << var_file << "\"." 131*ec63e07aSXin Li- << std::endl; 132*ec63e07aSXin Li- return false; 133*ec63e07aSXin Li- } 134*ec63e07aSXin Li- var = var_file.substr(0, eq_pos); 135*ec63e07aSXin Li- const std::string path = var_file.substr(eq_pos + 1, std::string::npos); 136*ec63e07aSXin Li- 137*ec63e07aSXin Li- size_t b, e; 138*ec63e07aSXin Li- val.erase().append(imp).append(" @'"); 139*ec63e07aSXin Li- // duplicate all the single quotes in @path to make a quoted string 140*ec63e07aSXin Li- for (b = 0; (e = path.find("'", b)) != std::string::npos; b = e + 1) { 141*ec63e07aSXin Li- val.append(path.substr(b, e - b + 1)).push_back('\''); 142*ec63e07aSXin Li+/** Writes output files for multiple file output */ 143*ec63e07aSXin Li+bool write_multi_output_files(char *output, const std::string &output_dir, bool show_output_file_names) { 144*ec63e07aSXin Li+ // If multiple file output is used, then iterate over each string from 145*ec63e07aSXin Li+ // the sequence of strings returned by jsonnet_evaluate_snippet_multi, 146*ec63e07aSXin Li+ // construct pairs of filename and content, and write each output file. 147*ec63e07aSXin Li+ std::map<std::string, std::string> r; 148*ec63e07aSXin Li+ for (const char *c = output; *c != '\0';) { 149*ec63e07aSXin Li+ const char *filename = c; 150*ec63e07aSXin Li+ const char *c2 = c; 151*ec63e07aSXin Li+ while (*c2 != '\0') ++c2; 152*ec63e07aSXin Li+ ++c2; 153*ec63e07aSXin Li+ const char *json = c2; 154*ec63e07aSXin Li+ while (*c2 != '\0') ++c2; 155*ec63e07aSXin Li+ ++c2; 156*ec63e07aSXin Li+ c = c2; 157*ec63e07aSXin Li+ r[filename] = json; 158*ec63e07aSXin Li+ } 159*ec63e07aSXin Li+ 160*ec63e07aSXin Li+ std::ostream *o; 161*ec63e07aSXin Li+ std::ofstream f; 162*ec63e07aSXin Li+ 163*ec63e07aSXin Li+ o = &std::cout; 164*ec63e07aSXin Li+ 165*ec63e07aSXin Li+ for (const auto &pair : r) { 166*ec63e07aSXin Li+ const std::string &new_content = pair.second; 167*ec63e07aSXin Li+ const std::string &filename = output_dir + pair.first; 168*ec63e07aSXin Li+ if (show_output_file_names) { 169*ec63e07aSXin Li+ (*o) << filename << std::endl; 170*ec63e07aSXin Li } 171*ec63e07aSXin Li- val.append(path.substr(b)).push_back('\''); 172*ec63e07aSXin Li- 173*ec63e07aSXin Li- return true; 174*ec63e07aSXin Li-} 175*ec63e07aSXin Li- 176*ec63e07aSXin Li-enum ArgStatus { 177*ec63e07aSXin Li- ARG_CONTINUE, 178*ec63e07aSXin Li- ARG_SUCCESS, 179*ec63e07aSXin Li- ARG_FAILURE, 180*ec63e07aSXin Li-}; 181*ec63e07aSXin Li- 182*ec63e07aSXin Li-/** Parse the command line arguments, configuring the Jsonnet VM context and 183*ec63e07aSXin Li- * populating the JsonnetConfig. 184*ec63e07aSXin Li- */ 185*ec63e07aSXin Li-static ArgStatus process_args(int argc, const char **argv, JsonnetConfig *config, JsonnetVm *vm) 186*ec63e07aSXin Li-{ 187*ec63e07aSXin Li- auto args = simplify_args(argc, argv); 188*ec63e07aSXin Li- std::vector<std::string> remaining_args; 189*ec63e07aSXin Li- 190*ec63e07aSXin Li- unsigned i = 0; 191*ec63e07aSXin Li- 192*ec63e07aSXin Li- for (; i < args.size(); ++i) { 193*ec63e07aSXin Li- const std::string &arg = args[i]; 194*ec63e07aSXin Li- if (arg == "-h" || arg == "--help") { 195*ec63e07aSXin Li- usage(std::cout); 196*ec63e07aSXin Li- return ARG_SUCCESS; 197*ec63e07aSXin Li- } else if (arg == "-v" || arg == "--version") { 198*ec63e07aSXin Li- version(std::cout); 199*ec63e07aSXin Li- return ARG_SUCCESS; 200*ec63e07aSXin Li- } else if (arg == "-e" || arg == "--exec") { 201*ec63e07aSXin Li- config->filenameIsCode = true; 202*ec63e07aSXin Li- } else if (arg == "-o" || arg == "--output-file") { 203*ec63e07aSXin Li- std::string output_file = next_arg(i, args); 204*ec63e07aSXin Li- if (output_file.length() == 0) { 205*ec63e07aSXin Li- std::cerr << "ERROR: -o argument was empty string" << std::endl; 206*ec63e07aSXin Li- return ARG_FAILURE; 207*ec63e07aSXin Li- } 208*ec63e07aSXin Li- config->outputFile = output_file; 209*ec63e07aSXin Li- } else if (arg == "--") { 210*ec63e07aSXin Li- // All subsequent args are not options. 211*ec63e07aSXin Li- while ((++i) < args.size()) 212*ec63e07aSXin Li- remaining_args.push_back(args[i]); 213*ec63e07aSXin Li- break; 214*ec63e07aSXin Li- } else if (arg == "-s" || arg == "--max-stack") { 215*ec63e07aSXin Li- long l = strtol_check(next_arg(i, args)); 216*ec63e07aSXin Li- if (l < 1) { 217*ec63e07aSXin Li- std::cerr << "ERROR: invalid --max-stack value: " << l << std::endl; 218*ec63e07aSXin Li- return ARG_FAILURE; 219*ec63e07aSXin Li- } 220*ec63e07aSXin Li- jsonnet_max_stack(vm, l); 221*ec63e07aSXin Li- } else if (arg == "-J" || arg == "--jpath") { 222*ec63e07aSXin Li- std::string dir = next_arg(i, args); 223*ec63e07aSXin Li- if (dir.length() == 0) { 224*ec63e07aSXin Li- std::cerr << "ERROR: -J argument was empty string" << std::endl; 225*ec63e07aSXin Li- return ARG_FAILURE; 226*ec63e07aSXin Li- } 227*ec63e07aSXin Li- if (dir[dir.length() - 1] != '/') { 228*ec63e07aSXin Li- dir += '/'; 229*ec63e07aSXin Li- } 230*ec63e07aSXin Li- jsonnet_jpath_add(vm, dir.c_str()); 231*ec63e07aSXin Li- } else if (arg == "-V" || arg == "--ext-str") { 232*ec63e07aSXin Li- std::string var, val; 233*ec63e07aSXin Li- if (!get_var_val(next_arg(i, args), var, val)) 234*ec63e07aSXin Li- return ARG_FAILURE; 235*ec63e07aSXin Li- jsonnet_ext_var(vm, var.c_str(), val.c_str()); 236*ec63e07aSXin Li- } else if (arg == "-E" || arg == "--var" || arg == "--env") { 237*ec63e07aSXin Li- // TODO(dcunnin): Delete this in a future release. 238*ec63e07aSXin Li- std::cerr << "WARNING: jsonnet eval -E, --var and --env are deprecated," 239*ec63e07aSXin Li- << " please use -V or --ext-str." << std::endl; 240*ec63e07aSXin Li- std::string var, val; 241*ec63e07aSXin Li- if (!get_var_val(next_arg(i, args), var, val)) 242*ec63e07aSXin Li- return ARG_FAILURE; 243*ec63e07aSXin Li- jsonnet_ext_var(vm, var.c_str(), val.c_str()); 244*ec63e07aSXin Li- } else if (arg == "--ext-str-file") { 245*ec63e07aSXin Li- std::string var, val; 246*ec63e07aSXin Li- if (!get_var_file(next_arg(i, args), "importstr", var, val)) 247*ec63e07aSXin Li- return ARG_FAILURE; 248*ec63e07aSXin Li- jsonnet_ext_code(vm, var.c_str(), val.c_str()); 249*ec63e07aSXin Li- } else if (arg == "-F" || arg == "--file") { 250*ec63e07aSXin Li- // TODO(dcunnin): Delete this in a future release. 251*ec63e07aSXin Li- std::cerr << "WARNING: jsonnet eval -F and --file are deprecated," 252*ec63e07aSXin Li- << " please use --ext-str-file." << std::endl; 253*ec63e07aSXin Li- std::string var, val; 254*ec63e07aSXin Li- if (!get_var_file(next_arg(i, args), "importstr", var, val)) 255*ec63e07aSXin Li- return ARG_FAILURE; 256*ec63e07aSXin Li- jsonnet_ext_code(vm, var.c_str(), val.c_str()); 257*ec63e07aSXin Li- } else if (arg == "--ext-code") { 258*ec63e07aSXin Li- std::string var, val; 259*ec63e07aSXin Li- if (!get_var_val(next_arg(i, args), var, val)) 260*ec63e07aSXin Li- return ARG_FAILURE; 261*ec63e07aSXin Li- jsonnet_ext_code(vm, var.c_str(), val.c_str()); 262*ec63e07aSXin Li- } else if (arg == "--code-var" || arg == "--code-env") { 263*ec63e07aSXin Li- // TODO(dcunnin): Delete this in a future release. 264*ec63e07aSXin Li- std::cerr << "WARNING: jsonnet eval --code-var and --code-env are deprecated," 265*ec63e07aSXin Li- << " please use --ext-code." << std::endl; 266*ec63e07aSXin Li- std::string var, val; 267*ec63e07aSXin Li- if (!get_var_val(next_arg(i, args), var, val)) 268*ec63e07aSXin Li- return ARG_FAILURE; 269*ec63e07aSXin Li- jsonnet_ext_code(vm, var.c_str(), val.c_str()); 270*ec63e07aSXin Li- } else if (arg == "--ext-code-file") { 271*ec63e07aSXin Li- std::string var, val; 272*ec63e07aSXin Li- if (!get_var_file(next_arg(i, args), "import", var, val)) 273*ec63e07aSXin Li- return ARG_FAILURE; 274*ec63e07aSXin Li- jsonnet_ext_code(vm, var.c_str(), val.c_str()); 275*ec63e07aSXin Li- } else if (arg == "--code-file") { 276*ec63e07aSXin Li- // TODO(dcunnin): Delete this in a future release. 277*ec63e07aSXin Li- std::cerr << "WARNING: jsonnet eval --code-file is deprecated," 278*ec63e07aSXin Li- << " please use --ext-code-file." << std::endl; 279*ec63e07aSXin Li- std::string var, val; 280*ec63e07aSXin Li- if (!get_var_file(next_arg(i, args), "import", var, val)) 281*ec63e07aSXin Li- return ARG_FAILURE; 282*ec63e07aSXin Li- jsonnet_ext_code(vm, var.c_str(), val.c_str()); 283*ec63e07aSXin Li- } else if (arg == "-A" || arg == "--tla-str") { 284*ec63e07aSXin Li- std::string var, val; 285*ec63e07aSXin Li- if (!get_var_val(next_arg(i, args), var, val)) 286*ec63e07aSXin Li- return ARG_FAILURE; 287*ec63e07aSXin Li- jsonnet_tla_var(vm, var.c_str(), val.c_str()); 288*ec63e07aSXin Li- } else if (arg == "--tla-str-file") { 289*ec63e07aSXin Li- std::string var, val; 290*ec63e07aSXin Li- if (!get_var_file(next_arg(i, args), "importstr", var, val)) 291*ec63e07aSXin Li- return ARG_FAILURE; 292*ec63e07aSXin Li- jsonnet_tla_code(vm, var.c_str(), val.c_str()); 293*ec63e07aSXin Li- } else if (arg == "--tla-code") { 294*ec63e07aSXin Li- std::string var, val; 295*ec63e07aSXin Li- if (!get_var_val(next_arg(i, args), var, val)) 296*ec63e07aSXin Li- return ARG_FAILURE; 297*ec63e07aSXin Li- jsonnet_tla_code(vm, var.c_str(), val.c_str()); 298*ec63e07aSXin Li- } else if (arg == "--tla-code-file") { 299*ec63e07aSXin Li- std::string var, val; 300*ec63e07aSXin Li- if (!get_var_file(next_arg(i, args), "import", var, val)) 301*ec63e07aSXin Li- return ARG_FAILURE; 302*ec63e07aSXin Li- jsonnet_tla_code(vm, var.c_str(), val.c_str()); 303*ec63e07aSXin Li- 304*ec63e07aSXin Li- } else if (arg == "--gc-min-objects") { 305*ec63e07aSXin Li- long l = strtol_check(next_arg(i, args)); 306*ec63e07aSXin Li- if (l < 0) { 307*ec63e07aSXin Li- std::cerr << "ERROR: invalid --gc-min-objects value: " << l << std::endl; 308*ec63e07aSXin Li- return ARG_FAILURE; 309*ec63e07aSXin Li- } 310*ec63e07aSXin Li- jsonnet_gc_min_objects(vm, l); 311*ec63e07aSXin Li- } else if (arg == "-t" || arg == "--max-trace") { 312*ec63e07aSXin Li- long l = strtol_check(next_arg(i, args)); 313*ec63e07aSXin Li- if (l < 0) { 314*ec63e07aSXin Li- std::cerr << "ERROR: invalid --max-trace value: " << l << std::endl; 315*ec63e07aSXin Li- return ARG_FAILURE; 316*ec63e07aSXin Li- } 317*ec63e07aSXin Li- jsonnet_max_trace(vm, l); 318*ec63e07aSXin Li- } else if (arg == "--gc-growth-trigger") { 319*ec63e07aSXin Li- std::string num = next_arg(i, args); 320*ec63e07aSXin Li- char *ep; 321*ec63e07aSXin Li- double v = std::strtod(num.c_str(), &ep); 322*ec63e07aSXin Li- if (*ep != '\0' || num.length() == 0) { 323*ec63e07aSXin Li- std::cerr << "ERROR: invalid number \"" << num << "\"" << std::endl; 324*ec63e07aSXin Li- return ARG_FAILURE; 325*ec63e07aSXin Li- } 326*ec63e07aSXin Li- if (v < 0) { 327*ec63e07aSXin Li- std::cerr << "ERROR: invalid --gc-growth-trigger \"" << num << "\"" 328*ec63e07aSXin Li- << std::endl; 329*ec63e07aSXin Li- return ARG_FAILURE; 330*ec63e07aSXin Li- } 331*ec63e07aSXin Li- jsonnet_gc_growth_trigger(vm, v); 332*ec63e07aSXin Li- } else if (arg == "-m" || arg == "--multi") { 333*ec63e07aSXin Li- config->evalMulti = true; 334*ec63e07aSXin Li- std::string output_dir = next_arg(i, args); 335*ec63e07aSXin Li- if (output_dir.length() == 0) { 336*ec63e07aSXin Li- std::cerr << "ERROR: -m argument was empty string" << std::endl; 337*ec63e07aSXin Li- return ARG_FAILURE; 338*ec63e07aSXin Li- } 339*ec63e07aSXin Li- if (output_dir[output_dir.length() - 1] != '/') { 340*ec63e07aSXin Li- output_dir += '/'; 341*ec63e07aSXin Li- } 342*ec63e07aSXin Li- config->evalMultiOutputDir = output_dir; 343*ec63e07aSXin Li- } else if (arg == "-y" || arg == "--yaml-stream") { 344*ec63e07aSXin Li- config->evalStream = true; 345*ec63e07aSXin Li- } else if (arg == "-S" || arg == "--string") { 346*ec63e07aSXin Li- jsonnet_string_output(vm, 1); 347*ec63e07aSXin Li- } else if (arg.length() > 1 && arg[0] == '-') { 348*ec63e07aSXin Li- std::cerr << "ERROR: unrecognized argument: " << arg << std::endl; 349*ec63e07aSXin Li- return ARG_FAILURE; 350*ec63e07aSXin Li- } else { 351*ec63e07aSXin Li- remaining_args.push_back(args[i]); 352*ec63e07aSXin Li+ { 353*ec63e07aSXin Li+ std::ifstream exists(filename.c_str()); 354*ec63e07aSXin Li+ if (exists.good()) { 355*ec63e07aSXin Li+ std::string existing_content; 356*ec63e07aSXin Li+ existing_content.assign(std::istreambuf_iterator<char>(exists), 357*ec63e07aSXin Li+ std::istreambuf_iterator<char>()); 358*ec63e07aSXin Li+ if (existing_content == new_content) { 359*ec63e07aSXin Li+ // Do not bump the timestamp on the file if its content is 360*ec63e07aSXin Li+ // the same. This may trigger other tools (e.g. make) to do 361*ec63e07aSXin Li+ // unnecessary work. 362*ec63e07aSXin Li+ continue; 363*ec63e07aSXin Li } 364*ec63e07aSXin Li+ } 365*ec63e07aSXin Li } 366*ec63e07aSXin Li- 367*ec63e07aSXin Li- const char *want = config->filenameIsCode ? "code" : "filename"; 368*ec63e07aSXin Li- if (remaining_args.size() == 0) { 369*ec63e07aSXin Li- std::cerr << "ERROR: must give " << want << "\n" << std::endl; 370*ec63e07aSXin Li- usage(std::cerr); 371*ec63e07aSXin Li- return ARG_FAILURE; 372*ec63e07aSXin Li- } 373*ec63e07aSXin Li- 374*ec63e07aSXin Li- if (remaining_args.size() > 1) { 375*ec63e07aSXin Li- std::string filename = remaining_args[0]; 376*ec63e07aSXin Li- std::cerr << "ERROR: only one " << want << " is allowed\n" << std::endl; 377*ec63e07aSXin Li- return ARG_FAILURE; 378*ec63e07aSXin Li- } 379*ec63e07aSXin Li- config->inputFiles = remaining_args; 380*ec63e07aSXin Li- return ARG_CONTINUE; 381*ec63e07aSXin Li-} 382*ec63e07aSXin Li- 383*ec63e07aSXin Li-/** Writes output files for multiple file output */ 384*ec63e07aSXin Li-static bool write_multi_output_files(JsonnetVm *vm, char *output, const std::string &output_dir, 385*ec63e07aSXin Li- const std::string &output_file) 386*ec63e07aSXin Li-{ 387*ec63e07aSXin Li- // If multiple file output is used, then iterate over each string from 388*ec63e07aSXin Li- // the sequence of strings returned by jsonnet_evaluate_snippet_multi, 389*ec63e07aSXin Li- // construct pairs of filename and content, and write each output file. 390*ec63e07aSXin Li- std::map<std::string, std::string> r; 391*ec63e07aSXin Li- for (const char *c = output; *c != '\0';) { 392*ec63e07aSXin Li- const char *filename = c; 393*ec63e07aSXin Li- const char *c2 = c; 394*ec63e07aSXin Li- while (*c2 != '\0') 395*ec63e07aSXin Li- ++c2; 396*ec63e07aSXin Li- ++c2; 397*ec63e07aSXin Li- const char *json = c2; 398*ec63e07aSXin Li- while (*c2 != '\0') 399*ec63e07aSXin Li- ++c2; 400*ec63e07aSXin Li- ++c2; 401*ec63e07aSXin Li- c = c2; 402*ec63e07aSXin Li- r[filename] = json; 403*ec63e07aSXin Li- } 404*ec63e07aSXin Li- jsonnet_realloc(vm, output, 0); 405*ec63e07aSXin Li- 406*ec63e07aSXin Li- std::ostream *o; 407*ec63e07aSXin Li std::ofstream f; 408*ec63e07aSXin Li- 409*ec63e07aSXin Li- if (output_file.empty()) { 410*ec63e07aSXin Li- o = &std::cout; 411*ec63e07aSXin Li- } else { 412*ec63e07aSXin Li- f.open(output_file.c_str()); 413*ec63e07aSXin Li- if (!f.good()) { 414*ec63e07aSXin Li- std::string msg = "Writing to output file: " + output_file; 415*ec63e07aSXin Li- perror(msg.c_str()); 416*ec63e07aSXin Li- return false; 417*ec63e07aSXin Li- } 418*ec63e07aSXin Li- o = &f; 419*ec63e07aSXin Li+ f.open(filename.c_str()); 420*ec63e07aSXin Li+ if (!f.good()) { 421*ec63e07aSXin Li+ std::string msg = "Opening output file: " + filename; 422*ec63e07aSXin Li+ perror(msg.c_str()); 423*ec63e07aSXin Li+ return false; 424*ec63e07aSXin Li+ } 425*ec63e07aSXin Li+ f << new_content; 426*ec63e07aSXin Li+ f.close(); 427*ec63e07aSXin Li+ if (!f.good()) { 428*ec63e07aSXin Li+ std::string msg = "Writing to output file: " + filename; 429*ec63e07aSXin Li+ perror(msg.c_str()); 430*ec63e07aSXin Li+ return false; 431*ec63e07aSXin Li } 432*ec63e07aSXin Li+ } 433*ec63e07aSXin Li 434*ec63e07aSXin Li- for (const auto &pair : r) { 435*ec63e07aSXin Li- const std::string &new_content = pair.second; 436*ec63e07aSXin Li- const std::string &filename = output_dir + pair.first; 437*ec63e07aSXin Li- (*o) << filename << std::endl; 438*ec63e07aSXin Li- { 439*ec63e07aSXin Li- std::ifstream exists(filename.c_str()); 440*ec63e07aSXin Li- if (exists.good()) { 441*ec63e07aSXin Li- std::string existing_content; 442*ec63e07aSXin Li- existing_content.assign(std::istreambuf_iterator<char>(exists), 443*ec63e07aSXin Li- std::istreambuf_iterator<char>()); 444*ec63e07aSXin Li- if (existing_content == new_content) { 445*ec63e07aSXin Li- // Do not bump the timestamp on the file if its content is 446*ec63e07aSXin Li- // the same. This may trigger other tools (e.g. make) to do 447*ec63e07aSXin Li- // unnecessary work. 448*ec63e07aSXin Li- continue; 449*ec63e07aSXin Li- } 450*ec63e07aSXin Li- } 451*ec63e07aSXin Li- } 452*ec63e07aSXin Li- std::ofstream f; 453*ec63e07aSXin Li- f.open(filename.c_str()); 454*ec63e07aSXin Li- if (!f.good()) { 455*ec63e07aSXin Li- std::string msg = "Opening output file: " + filename; 456*ec63e07aSXin Li- perror(msg.c_str()); 457*ec63e07aSXin Li- return false; 458*ec63e07aSXin Li- } 459*ec63e07aSXin Li- f << new_content; 460*ec63e07aSXin Li- f.close(); 461*ec63e07aSXin Li- if (!f.good()) { 462*ec63e07aSXin Li- std::string msg = "Writing to output file: " + filename; 463*ec63e07aSXin Li- perror(msg.c_str()); 464*ec63e07aSXin Li- return false; 465*ec63e07aSXin Li- } 466*ec63e07aSXin Li- } 467*ec63e07aSXin Li+ std::cout.flush(); 468*ec63e07aSXin Li 469*ec63e07aSXin Li- if (output_file.empty()) { 470*ec63e07aSXin Li- std::cout.flush(); 471*ec63e07aSXin Li- } else { 472*ec63e07aSXin Li- f.close(); 473*ec63e07aSXin Li- if (!f.good()) { 474*ec63e07aSXin Li- std::string msg = "Writing to output file: " + output_file; 475*ec63e07aSXin Li- perror(msg.c_str()); 476*ec63e07aSXin Li- return false; 477*ec63e07aSXin Li- } 478*ec63e07aSXin Li- } 479*ec63e07aSXin Li- return true; 480*ec63e07aSXin Li+ return true; 481*ec63e07aSXin Li } 482*ec63e07aSXin Li 483*ec63e07aSXin Li /** Writes output files for YAML stream output */ 484*ec63e07aSXin Li-static bool write_output_stream(JsonnetVm *vm, char *output, const std::string &output_file) 485*ec63e07aSXin Li-{ 486*ec63e07aSXin Li- std::ostream *o; 487*ec63e07aSXin Li- std::ofstream f; 488*ec63e07aSXin Li- 489*ec63e07aSXin Li- if (output_file.empty()) { 490*ec63e07aSXin Li- o = &std::cout; 491*ec63e07aSXin Li- } else { 492*ec63e07aSXin Li- f.open(output_file.c_str()); 493*ec63e07aSXin Li- if (!f.good()) { 494*ec63e07aSXin Li- std::string msg = "Writing to output file: " + output_file; 495*ec63e07aSXin Li- perror(msg.c_str()); 496*ec63e07aSXin Li- return false; 497*ec63e07aSXin Li- } 498*ec63e07aSXin Li- o = &f; 499*ec63e07aSXin Li- } 500*ec63e07aSXin Li- 501*ec63e07aSXin Li- // If YAML stream output is used, then iterate over each string from 502*ec63e07aSXin Li- // the sequence of strings returned by jsonnet_evaluate_snippet_stream, 503*ec63e07aSXin Li- // and add the --- and ... as defined by the YAML spec. 504*ec63e07aSXin Li- std::vector<std::string> r; 505*ec63e07aSXin Li- for (const char *c = output; *c != '\0';) { 506*ec63e07aSXin Li- const char *json = c; 507*ec63e07aSXin Li- while (*c != '\0') 508*ec63e07aSXin Li- ++c; 509*ec63e07aSXin Li- ++c; 510*ec63e07aSXin Li- r.emplace_back(json); 511*ec63e07aSXin Li- } 512*ec63e07aSXin Li- jsonnet_realloc(vm, output, 0); 513*ec63e07aSXin Li- for (const auto &str : r) { 514*ec63e07aSXin Li- (*o) << "---\n"; 515*ec63e07aSXin Li- (*o) << str; 516*ec63e07aSXin Li- } 517*ec63e07aSXin Li- if (r.size() > 0) 518*ec63e07aSXin Li- (*o) << "...\n"; 519*ec63e07aSXin Li- o->flush(); 520*ec63e07aSXin Li- 521*ec63e07aSXin Li- if (output_file.empty()) { 522*ec63e07aSXin Li- std::cout.flush(); 523*ec63e07aSXin Li- } else { 524*ec63e07aSXin Li- f.close(); 525*ec63e07aSXin Li- if (!f.good()) { 526*ec63e07aSXin Li- std::string msg = "Writing to output file: " + output_file; 527*ec63e07aSXin Li- perror(msg.c_str()); 528*ec63e07aSXin Li- return false; 529*ec63e07aSXin Li- } 530*ec63e07aSXin Li+bool write_output_stream(char *output, const std::string &output_file) { 531*ec63e07aSXin Li+ std::ostream *o; 532*ec63e07aSXin Li+ std::ofstream f; 533*ec63e07aSXin Li+ 534*ec63e07aSXin Li+ if (output_file.empty()) { 535*ec63e07aSXin Li+ o = &std::cout; 536*ec63e07aSXin Li+ } else { 537*ec63e07aSXin Li+ f.open(output_file.c_str()); 538*ec63e07aSXin Li+ if (!f.good()) { 539*ec63e07aSXin Li+ std::string msg = "Writing to output file: " + output_file; 540*ec63e07aSXin Li+ perror(msg.c_str()); 541*ec63e07aSXin Li+ return false; 542*ec63e07aSXin Li+ } 543*ec63e07aSXin Li+ o = &f; 544*ec63e07aSXin Li+ } 545*ec63e07aSXin Li+ 546*ec63e07aSXin Li+ // If YAML stream output is used, then iterate over each string from 547*ec63e07aSXin Li+ // the sequence of strings returned by jsonnet_evaluate_snippet_stream, 548*ec63e07aSXin Li+ // and add the --- and ... as defined by the YAML spec. 549*ec63e07aSXin Li+ std::vector<std::string> r; 550*ec63e07aSXin Li+ for (const char *c = output; *c != '\0';) { 551*ec63e07aSXin Li+ const char *json = c; 552*ec63e07aSXin Li+ while (*c != '\0') ++c; 553*ec63e07aSXin Li+ ++c; 554*ec63e07aSXin Li+ r.emplace_back(json); 555*ec63e07aSXin Li+ } 556*ec63e07aSXin Li+ 557*ec63e07aSXin Li+ for (const auto &str : r) { 558*ec63e07aSXin Li+ (*o) << "---\n"; 559*ec63e07aSXin Li+ (*o) << str; 560*ec63e07aSXin Li+ } 561*ec63e07aSXin Li+ if (r.size() > 0) (*o) << "...\n"; 562*ec63e07aSXin Li+ o->flush(); 563*ec63e07aSXin Li+ 564*ec63e07aSXin Li+ if (output_file.empty()) { 565*ec63e07aSXin Li+ std::cout.flush(); 566*ec63e07aSXin Li+ } else { 567*ec63e07aSXin Li+ f.close(); 568*ec63e07aSXin Li+ if (!f.good()) { 569*ec63e07aSXin Li+ std::string msg = "Writing to output file: " + output_file; 570*ec63e07aSXin Li+ perror(msg.c_str()); 571*ec63e07aSXin Li+ return false; 572*ec63e07aSXin Li } 573*ec63e07aSXin Li+ } 574*ec63e07aSXin Li 575*ec63e07aSXin Li- return true; 576*ec63e07aSXin Li-} 577*ec63e07aSXin Li- 578*ec63e07aSXin Li-int main(int argc, const char **argv) 579*ec63e07aSXin Li-{ 580*ec63e07aSXin Li- try { 581*ec63e07aSXin Li- JsonnetVm *vm = jsonnet_make(); 582*ec63e07aSXin Li- JsonnetConfig config; 583*ec63e07aSXin Li- if (const char *jsonnet_path_env = getenv("JSONNET_PATH")) { 584*ec63e07aSXin Li- std::list<std::string> jpath; 585*ec63e07aSXin Li- std::istringstream iss(jsonnet_path_env); 586*ec63e07aSXin Li- std::string path; 587*ec63e07aSXin Li- while (std::getline(iss, path, PATH_SEP)) { 588*ec63e07aSXin Li- jpath.push_front(path); 589*ec63e07aSXin Li- } 590*ec63e07aSXin Li- for (const std::string &path : jpath) { 591*ec63e07aSXin Li- jsonnet_jpath_add(vm, path.c_str()); 592*ec63e07aSXin Li- } 593*ec63e07aSXin Li- } 594*ec63e07aSXin Li- ArgStatus arg_status = process_args(argc, argv, &config, vm); 595*ec63e07aSXin Li- if (arg_status != ARG_CONTINUE) { 596*ec63e07aSXin Li- jsonnet_destroy(vm); 597*ec63e07aSXin Li- return arg_status == ARG_SUCCESS ? EXIT_SUCCESS : EXIT_FAILURE; 598*ec63e07aSXin Li- } 599*ec63e07aSXin Li- 600*ec63e07aSXin Li- // Evaluate input Jsonnet and handle any errors from Jsonnet VM. 601*ec63e07aSXin Li- int error; 602*ec63e07aSXin Li- char *output; 603*ec63e07aSXin Li- assert(config.inputFiles.size() == 1); 604*ec63e07aSXin Li- 605*ec63e07aSXin Li- // Read input file. 606*ec63e07aSXin Li- std::string input; 607*ec63e07aSXin Li- if (!read_input(config.filenameIsCode, &config.inputFiles[0], &input)) { 608*ec63e07aSXin Li- jsonnet_destroy(vm); 609*ec63e07aSXin Li- return EXIT_FAILURE; 610*ec63e07aSXin Li- } 611*ec63e07aSXin Li- 612*ec63e07aSXin Li- if (config.evalMulti) { 613*ec63e07aSXin Li- output = jsonnet_evaluate_snippet_multi( 614*ec63e07aSXin Li- vm, config.inputFiles[0].c_str(), input.c_str(), &error); 615*ec63e07aSXin Li- } else if (config.evalStream) { 616*ec63e07aSXin Li- output = jsonnet_evaluate_snippet_stream( 617*ec63e07aSXin Li- vm, config.inputFiles[0].c_str(), input.c_str(), &error); 618*ec63e07aSXin Li- } else { 619*ec63e07aSXin Li- output = jsonnet_evaluate_snippet( 620*ec63e07aSXin Li- vm, config.inputFiles[0].c_str(), input.c_str(), &error); 621*ec63e07aSXin Li- } 622*ec63e07aSXin Li- 623*ec63e07aSXin Li- if (error) { 624*ec63e07aSXin Li- std::cerr << output; 625*ec63e07aSXin Li- jsonnet_realloc(vm, output, 0); 626*ec63e07aSXin Li- jsonnet_destroy(vm); 627*ec63e07aSXin Li- return EXIT_FAILURE; 628*ec63e07aSXin Li- } 629*ec63e07aSXin Li- 630*ec63e07aSXin Li- // Write output JSON. 631*ec63e07aSXin Li- if (config.evalMulti) { 632*ec63e07aSXin Li- if (!write_multi_output_files( 633*ec63e07aSXin Li- vm, output, config.evalMultiOutputDir, config.outputFile)) { 634*ec63e07aSXin Li- jsonnet_destroy(vm); 635*ec63e07aSXin Li- return EXIT_FAILURE; 636*ec63e07aSXin Li- } 637*ec63e07aSXin Li- } else if (config.evalStream) { 638*ec63e07aSXin Li- if (!write_output_stream(vm, output, config.outputFile)) { 639*ec63e07aSXin Li- jsonnet_destroy(vm); 640*ec63e07aSXin Li- return EXIT_FAILURE; 641*ec63e07aSXin Li- } 642*ec63e07aSXin Li- } else { 643*ec63e07aSXin Li- bool successful = write_output_file(output, config.outputFile); 644*ec63e07aSXin Li- jsonnet_realloc(vm, output, 0); 645*ec63e07aSXin Li- if (!successful) { 646*ec63e07aSXin Li- jsonnet_destroy(vm); 647*ec63e07aSXin Li- return EXIT_FAILURE; 648*ec63e07aSXin Li- } 649*ec63e07aSXin Li- } 650*ec63e07aSXin Li- 651*ec63e07aSXin Li- jsonnet_destroy(vm); 652*ec63e07aSXin Li- return EXIT_SUCCESS; 653*ec63e07aSXin Li- 654*ec63e07aSXin Li- } catch (const std::bad_alloc &) { 655*ec63e07aSXin Li- // Avoid further allocation attempts 656*ec63e07aSXin Li- fputs("Internal out-of-memory error (please report this)\n", stderr); 657*ec63e07aSXin Li- } catch (const std::exception &e) { 658*ec63e07aSXin Li- std::cerr << "Internal error (please report this): " << e.what() << std::endl; 659*ec63e07aSXin Li- } catch (...) { 660*ec63e07aSXin Li- std::cerr << "An unknown exception occurred (please report this)." << std::endl; 661*ec63e07aSXin Li- } 662*ec63e07aSXin Li- return EXIT_FAILURE; 663*ec63e07aSXin Li+ return true; 664*ec63e07aSXin Li } 665