1 /**
2    Command-line options
3  */
4 module dpp.runtime.options;
5 
6 @safe:
7 
8 version(Windows)
9     enum exeExtension = ".exe";
10 else
11     enum exeExtension = "";
12 
13 
14 struct Options {
15 
16     enum usage = "Usage: d++ [options] [D compiler options] <filename.dpp> [D compiler args]";
17 
18     string[] dppFileNames;
19     string indentation;
20     bool debugOutput;
21     string[] includePaths;
22     bool keepPreCppFiles;
23     bool keepDlangFiles;
24     bool parseAsCpp;
25     bool preprocessOnly;
26     string dlangCompiler = "dmd";
27     string[] dlangCompilerArgs;
28     string[] defines;
29     bool earlyExit;
30     bool hardFail;
31     bool cppStdLib;
32     bool ignoreMacros;
33     bool detailedUntranslatable;
34     string[] ignoredNamespaces;
35     string[] ignoredCursors;
36     bool alwaysScopedEnums;
37     string cppStandard = "c++17";
38     string[] clangOptions;
39 
40     this(string[] args) {
41 
42         import clang: systemPaths;
43         import std.exception: enforce;
44         import std.path: stripExtension, extension, buildPath, absolutePath;
45         import std.file: tempDir;
46         import std.algorithm: map, filter, canFind, startsWith;
47         import std.array: array;
48         import std.conv: text;
49 
50         parseArgs(args);
51         if(earlyExit) return;
52 
53         if(preprocessOnly)
54             keepDlangFiles = true;
55 
56         dppFileNames = args.filter!(a => a.extension == ".dpp").array;
57         enforce(dppFileNames.length != 0, "No .dpp input file specified\n" ~ usage);
58 
59         // Remove the name of this binary and the name of the .dpp input file from args
60         // so that a D compiler can use the remaining entries.
61         dlangCompilerArgs =
62             args[1..$].filter!(a => a.extension != ".dpp").array ~
63             dFileNames;
64 
65         // if no -of option is given, default to the name of the .dpp file
66         if(!dlangCompilerArgs.canFind!(a => a.startsWith("-of")) && !dlangCompilerArgs.canFind("-c"))
67             dlangCompilerArgs ~= "-of" ~
68                 args.
69                 filter!(a => a.extension == ".dpp" || a.extension == ".d")
70                 .front
71                 .stripExtension
72                 ~ exeExtension;
73 
74         version(Windows)
75             assert(!cppStdLib, "C++ std lib functionality not implemented yet for Windows");
76 
77         if(cppStdLib) {
78             dlangCompilerArgs ~= "-L-lstdc++";
79             parseAsCpp = true;
80         }
81 
82         includePaths = systemPaths ~ includePaths;
83     }
84 
85     string[] dFileNames() @safe pure const {
86         import std.algorithm: map;
87         import std.array: array;
88         return dppFileNames.map!toDFileName.array;
89     }
90 
91     static string toDFileName(in string dppFileName) @safe pure nothrow {
92         import std.path: stripExtension;
93         return dppFileName.stripExtension ~ ".d";
94     }
95 
96     private void parseArgs(ref string[] args) {
97         import std.getopt: getopt, defaultGetoptPrinter, config;
98         auto helpInfo =
99             getopt(
100                 args,
101                 config.passThrough,
102                 "print-cursors", "Print debug information", &debugOutput,
103                 "include-path", "Include paths", &includePaths,
104                 "keep-pre-cpp-files", "Do not delete the temporary pre-preprocessed file", &keepPreCppFiles,
105                 "keep-d-files", "Do not delete the temporary D file to be compiled", &keepDlangFiles,
106                 "preprocess-only", "Only transform the .dpp file into a .d file, don't compile", &preprocessOnly,
107                 "compiler", "D compiler to use", &dlangCompiler,
108                 "parse-as-cpp", "Parse header as C++", &parseAsCpp,
109                 "define", "C Preprocessor macro", &defines,
110                 "hard-fail", "Translate nothing if any part fails", &hardFail,
111                 "c++-std-lib", "Link to the C++ standard library", &cppStdLib,
112                 "ignore-macros", "Ignore preprocessor macros", &ignoreMacros,
113                 "ignore-ns", "Ignore a C++ namespace", &ignoredNamespaces,
114                 "ignore-cursor", "Ignore a C++ cursor", &ignoredCursors,
115                 "detailed-untranslatables", "Show details about untranslatable cursors", &detailedUntranslatable,
116                 "scoped-enums", "Don't redeclare enums to mimic C", &alwaysScopedEnums,
117                 "c++-standard", "The C++ language standard (e.g. \"c++14\")", &cppStandard,
118                 "clang-option", "Pass option to libclang", &clangOptions,
119             );
120 
121         if(helpInfo.helpWanted) {
122             () @trusted {
123                 defaultGetoptPrinter(usage, helpInfo.options);
124             }();
125             earlyExit = true;
126         }
127     }
128 
129     Options indent() pure nothrow const {
130         Options ret;
131         foreach(i, ref elt; ret.tupleof) {
132             static if(__traits(compiles, this.tupleof[i].dup))
133                 elt = this.tupleof[i].dup;
134             else
135                 elt = this.tupleof[i];
136         }
137 
138         ret.includePaths = includePaths.dup;
139         ret.defines = defines.dup;
140         ret.indentation = indentation ~ "    ";
141 
142         return ret;
143     }
144 
145     void log(T...)(auto ref T args) @trusted const {
146         version(unittest) import unit_threaded.io: writeln = writelnUt;
147         else import std.stdio: writeln;
148 
149         version(unittest) enum shouldLog = true;
150         else             const shouldLog = debugOutput;
151 
152         if(shouldLog)
153             debug writeln(indentation, args);
154     }
155 }