1 // Copyright 2016-2021 Doug Moen
2 // Licensed under the Apache License, version 2.0
3 // See accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0
4
5 extern "C" {
6 #include <unistd.h>
7 #include <getopt.h>
8 #include <string.h>
9 }
10 #include <iostream>
11 #include <fstream>
12
13 #include "config.h"
14 #include "export.h"
15 #include "repl.h"
16 #include "shapes.h"
17 #include "livemode.h"
18 #include "version.h"
19
20 #include <libcurv/context.h>
21 #include <libcurv/die.h>
22 #include <libcurv/exception.h>
23 #include <libcurv/gpu_program.h>
24 #include <libcurv/import.h>
25 #include <libcurv/output_file.h>
26 #include <libcurv/progdir.h>
27 #include <libcurv/program.h>
28 #include <libcurv/source.h>
29 #include <libcurv/system.h>
30
31 #include <libcurv/io/builtin.h>
32 #include <libcurv/io/import.h>
33 #include <libcurv/io/tempfile.h>
34 #include <libcurv/viewer/viewer.h>
35
36 namespace fs = curv::Filesystem;
37
38 curv::System&
make_system(const char * argv0,std::list<const char * > & libs,std::ostream & out,bool verbose,int depr)39 make_system(const char* argv0, std::list<const char*>& libs, std::ostream& out,
40 bool verbose, int depr)
41 {
42 static curv::System_Impl sys(out);
43 sys.verbose_ = verbose;
44 sys.depr_ = depr;
45 #ifndef _WIN32
46 if (isatty(2)) sys.use_colour_ = true;
47 #endif
48 try {
49 curv::io::add_builtins(sys);
50 curv::io::add_importers(sys);
51 if (argv0 != nullptr) {
52 const char* CURV_LIBDIR = getenv("CURV_LIBDIR");
53 namespace fs = boost::filesystem;
54 curv::Shared<const curv::String> stdlib;
55 if (CURV_LIBDIR != nullptr) {
56 if (CURV_LIBDIR[0] != '\0') {
57 fs::path stdlib_path = fs::path(CURV_LIBDIR) / "std.curv";
58 stdlib = curv::make_string(stdlib_path.string().c_str());
59 } else
60 stdlib = nullptr;
61 } else {
62 fs::path stdlib_path = fs::canonical(
63 curv::progdir(argv0) / "../lib/curv/std.curv");
64 stdlib = curv::make_string(stdlib_path.string().c_str());
65 }
66 sys.load_library(stdlib);
67 }
68 for (const char* lib : libs) {
69 sys.load_library(curv::make_string(lib));
70 }
71 return sys;
72 } catch (std::exception& e) {
73 sys.error(e);
74 exit(EXIT_FAILURE);
75 }
76 }
77
78 const char help_prefix[] =
79 "curv --help [-o format]\n"
80 " Display help information.\n"
81 " -o format : Display help for an output format, showing -O parameters.\n"
82 "curv --version\n"
83 " Display version information needed for bug reports.\n"
84 "curv [options]\n"
85 " Interactive command-line shell.\n"
86 "curv -l [-e] [options] filename\n"
87 " Live programming mode. Evaluate & display result each time file changes.\n"
88 " -e : Open editor window. $CURV_EDITOR overrides default editor.\n"
89 " With 'curv -le', filename is optional, defaults to 'new.curv'.\n"
90 "curv [-o arg] [-x] [options] filename\n"
91 " Batch mode. Evaluate file, display result or export to a file.\n"
92 " -o format : Convert to specified file format, write data to stdout.\n"
93 ;
94
95 const char help_infix[] =
96 " -o filename.ext : Export to file, using filename extension as format.\n"
97 " -x : Interpret filename argument as expression.\n"
98 "general options:\n"
99 " -v : Verbose & debug output.\n"
100 " --depr=N : Deprecation warning level 0, 1 or 2; default is 1.\n"
101 " -O name=value : Set parameter controlling the specified output format.\n"
102 " If '-o fmt' is specified, use 'curv --help -o fmt' for help.\n"
103 " If '-o fmt' is not specified, the following parameters are available:\n"
104 ;
105
106 const char help_suffix[] =
107 " $CURV_LIBDIR : Standard library directory, overrides PREFIX/lib/curv\n"
108 " -n : Don't include standard library.\n"
109 " -i file : Include specified library; may be repeated.\n"
110 " -N : don't read user config file.\n"
111 ;
112
113 int
main(int argc,char ** argv)114 main(int argc, char** argv)
115 {
116 const char* argv0 = argv[0];
117
118 // Parse arguments for general case.
119 const char* usestdlib = argv0;
120 bool useconfig = true;
121 fs::path opath;
122 using ExPtr = decltype(exporters)::const_iterator;
123 ExPtr exporter = exporters.end();
124 Export_Params::Options options;
125 bool verbose = false;
126 int depr = 1;
127 bool live = false;
128 std::list<const char*> libs;
129 bool expr = false;
130 const char* editor = nullptr;
131 bool help = false;
132 bool version = false;
133
134 constexpr int HELP = 1000;
135 constexpr int VERSION = 1001;
136 constexpr int DEPR = 1002;
137 static const char opts[] = ":o:O:lnNi:xev";
138 static struct option longopts[] = {
139 {"help", no_argument, nullptr, HELP },
140 {"version", no_argument, nullptr, VERSION },
141 {"depr", required_argument, nullptr, DEPR },
142 {nullptr, 0, nullptr, 0 }
143 };
144
145 int opt;
146 while ((opt = getopt_long(argc, argv, opts, longopts, NULL)) != -1)
147 {
148 switch (opt) {
149 case HELP:
150 help = true;
151 break;
152 case VERSION:
153 version = true;
154 break;
155 case DEPR:
156 depr = atoi(optarg);
157 break;
158 case 'o':
159 {
160 const char* oarg = optarg;
161 opath = oarg;
162 std::string ext_string = opath.extension().string();
163 const char* ext = ext_string.c_str();
164 const char* oname;
165 if (ext[0] == '.')
166 oname = &ext[1];
167 else {
168 oname = oarg;
169 opath.clear();
170 }
171 exporter = exporters.find(oname);
172 if (exporter == exporters.end()) {
173 std::cerr << "-o: format '" << oname << "' not supported.\n"
174 << "Use " << argv0 << " --help for help.\n";
175 return EXIT_FAILURE;
176 }
177 break;
178 }
179 case 'O':
180 {
181 char* eq = strchr(optarg, '=');
182 if (eq == nullptr) {
183 options[std::string(optarg)] = curv::make_string("");
184 } else {
185 *eq = '\0';
186 options[std::string(optarg)] = curv::make_string(eq+1);
187 *eq = '=';
188 }
189 break;
190 }
191 case 'l':
192 live = true;
193 break;
194 case 'n':
195 usestdlib = nullptr;
196 break;
197 case 'i':
198 libs.push_back(optarg);
199 break;
200 case 'N':
201 useconfig = false;
202 break;
203 case 'x':
204 expr = true;
205 break;
206 case 'e':
207 editor = getenv("CURV_EDITOR");
208 if (editor == nullptr || *editor == '\0')
209 {
210 #ifdef _WIN32
211 editor = "notepad";
212 #else
213 editor = "gedit --new-window --wait";
214 #endif
215 }
216 break;
217 case 'v':
218 verbose = true;
219 break;
220 case '?':
221 std::cerr << "-" << (char)optopt << ": unknown option\n"
222 << "Use " << argv0 << " --help for help.\n";
223 return EXIT_FAILURE;
224 case ':':
225 if (optopt > 255) {
226 for (auto o = longopts; o->name != nullptr; ++o) {
227 if (o->val == optopt) {
228 std::cerr << "--" << o->name;
229 break;
230 }
231 }
232 } else {
233 std::cerr << "-" << (char)optopt;
234 }
235 std::cerr << ": missing argument\n"
236 << "Use " << argv0 << " --help for help.\n";
237 return EXIT_FAILURE;
238 default:
239 curv::die("main: bad result from getopt()");
240 }
241 }
242 const char* filename;
243 if (optind >= argc) {
244 filename = nullptr;
245 } else if (argc - optind > 1) {
246 std::cerr << "too many filename arguments\n"
247 << "Use " << argv0 << " --help for help.\n";
248 return EXIT_FAILURE;
249 } else
250 filename = argv[optind];
251
252 // Validate arguments
253 if (live) {
254 if (exporter != exporters.end()) {
255 std::cerr << "-l and -o flags are not compatible.\n"
256 << "Use " << argv0 << " --help for help.\n";
257 return EXIT_FAILURE;
258 }
259 if (expr) {
260 std::cerr << "-l and -x flags are not compatible.\n"
261 << "Use " << argv0 << " --help for help.\n";
262 return EXIT_FAILURE;
263 }
264 }
265 if (filename == nullptr) {
266 if (editor != nullptr)
267 filename = "new.curv";
268 else if (expr || live || (exporter != exporters.end() && !help)) {
269 std::cerr << "missing filename argument\n"
270 << "Use " << argv0 << " --help for help.\n";
271 return EXIT_FAILURE;
272 }
273 }
274 if (editor && !live) {
275 std::cerr << "-e flag specified without -l flag.\n"
276 << "Use " << argv0 << " --help for help.\n";
277 return EXIT_FAILURE;
278 }
279
280 // Handle 'curv --help'.
281 if (help) {
282 if (exporter != exporters.end()) {
283 std::cout << exporter->second.synopsis << "\n";
284 exporter->second.describe_options(std::cout);
285 } else {
286 std::cout << help_prefix;
287 for (auto& ex : exporters) {
288 std::cout << " " << ex.first << " : "
289 << ex.second.synopsis << "\n";
290 }
291 std::cout << help_infix;
292 describe_viewer_options(std::cout, " ");
293 std::cout << help_suffix;
294 }
295 return EXIT_SUCCESS;
296 }
297
298 // Handle 'curv --version'.
299 if (version) {
300 print_version(std::cout);
301 return EXIT_SUCCESS;
302 }
303
304 // Create system, a precondition for parsing -O parameters.
305 // This can fail, so we do as much argument validation as possible
306 // before this point.
307 curv::System& sys(make_system(usestdlib, libs, std::cerr, verbose, depr));
308 atexit(curv::io::remove_all_tempfiles);
309
310 try {
311 Config config;
312 if (useconfig) {
313 config = get_config(sys, curv::make_symbol(
314 exporter == exporters.end() ? "viewer" : "export"));
315 }
316 Export_Params oparams(std::move(options), config, sys);
317 if (exporter != exporters.end())
318 oparams.format_ = exporter->first;
319 oparams.verbose_ = verbose;
320
321 curv::viewer::Viewer_Config viewer_config;
322 if (exporter == exporters.end())
323 parse_viewer_config(oparams, viewer_config);
324
325 // Finally, do stuff.
326 if (filename == nullptr) {
327 interactive_mode(sys, viewer_config);
328 return EXIT_SUCCESS;
329 }
330
331 if (live) {
332 return live_mode(sys, editor, filename, viewer_config);
333 }
334
335 // batch mode
336 curv::Program prog{sys};
337 if (expr) {
338 auto source = curv::make<curv::String_Source>("", filename);
339 prog.compile(std::move(source));
340 } else {
341 curv::import(curv::Filesystem::path(filename),
342 prog, curv::At_System(sys));
343 }
344 auto value = prog.eval();
345
346 if (exporter != exporters.end()) {
347 curv::Output_File ofile{sys};
348 if (opath.empty())
349 ofile.set_ostream(&std::cout);
350 else
351 ofile.set_path(opath);
352 exporter->second.call(value, prog, oparams, ofile);
353 ofile.commit();
354 } else {
355 curv::GPU_Program gpu_prog{prog};
356 if (gpu_prog.recognize(value, viewer_config)) {
357 print_shape(gpu_prog);
358 curv::viewer::Viewer viewer(viewer_config);
359 viewer.set_shape(std::move(gpu_prog.vshape_));
360 viewer.run();
361 } else {
362 std::cout << value << "\n";
363 }
364 }
365 } catch (std::exception& e) {
366 sys.error(e);
367 return EXIT_FAILURE;
368 }
369 return EXIT_SUCCESS;
370 }
371