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