1 /** 2 * Copyright: Copyright (c) 2017 Jacob Carlborg. All rights reserved. 3 * Authors: Jacob Carlborg 4 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0) 5 */ 6 module dstep.driver.CommandLine; 7 8 import std.typecons : tuple, Tuple; 9 import std.getopt; 10 11 import dstep.Configuration; 12 import dstep.translator.Options; 13 import dstep.core.Exceptions; 14 15 /** 16 * Processes command-line arguments 17 * 18 * Params: 19 * args = command-line arguments 20 * 21 * Returns: 22 * 2-element tuple with first element being aggregate struct of app 23 * configuration and second - getopt parse result 24 */ 25 auto parseCommandLine(string[] args) 26 { 27 import std.array : split; 28 import std.getopt; 29 30 Configuration config; 31 32 // Parse dstep own parameters: 33 34 void parseLanguage (string param, string value) 35 { 36 config.clangParams ~= "-x"; 37 config.clangParams ~= value; 38 39 switch (value) 40 { 41 case "c": 42 case "c-header": 43 config.language = Language.c; 44 break; 45 case "objective-c": 46 case "objective-c-header": 47 config.language = Language.objC; 48 break; 49 default: 50 throw new DStepException(`Unrecognized language "` ~ value ~ `"`); 51 } 52 } 53 54 bool forceObjectiveC; 55 56 auto splittedArgs = args.split("--"); 57 58 if (splittedArgs.length == 1) 59 args = splittedArgs[0]; 60 else if (splittedArgs.length == 2) 61 { 62 args = splittedArgs[0]; 63 config.clangParams = splittedArgs[1]; 64 } 65 66 auto helpInformation = getopt( 67 args, 68 std.getopt.config.passThrough, 69 std.getopt.config.caseSensitive, 70 "output|o", &config.output, 71 "objective-c", &forceObjectiveC, 72 "language|x", &parseLanguage, 73 makeGetOptArgs!config); 74 75 // remove dstep binary name (args[0]) 76 args = args[1 .. $]; 77 78 // Separate input files from clang parameters: 79 foreach (arg; args) 80 { 81 if (arg[0] == '-') 82 config.clangParams ~= arg; 83 else 84 config.inputFiles ~= arg; 85 } 86 87 // Post-processing of CLI 88 89 import std.algorithm : canFind; 90 91 if (forceObjectiveC) 92 config.clangParams ~= "-ObjC"; 93 94 if (config.clangParams.canFind("-ObjC")) 95 config.language = Language.objC; 96 97 return tuple(config, helpInformation); 98 } 99 100 unittest 101 { 102 import std.algorithm.searching : find; 103 import std.range.primitives : empty; 104 import std.meta : AliasSeq; 105 106 Configuration config; 107 GetoptResult getoptResult; 108 109 AliasSeq!(config, getoptResult) = parseCommandLine( 110 [ "dstep", "-Xpreprocessor", "-lsomething", "-x", "c-header", "file.h" ]); 111 assert(config.language == Language.c); 112 assert(config.inputFiles == [ "file.h" ]); 113 assert(config.clangParams == [ "-x", "c-header", "-Xpreprocessor", "-lsomething" ]); 114 assert(config.output == ""); 115 116 AliasSeq!(config, getoptResult) = parseCommandLine( 117 [ "dstep", "-ObjC", "file2.h", "--output=folder", "file.h" ]); 118 assert(config.language == Language.objC); 119 assert(config.inputFiles == [ "file2.h", "file.h" ]); 120 assert(config.clangParams == [ "-ObjC" ]); 121 assert(config.output == "folder"); 122 123 AliasSeq!(config, getoptResult) = parseCommandLine( 124 [ "dstep", "file.h", "--skip-definition", "foo" ]); 125 assert(!config.skipDefinitions.find("foo").empty); 126 127 AliasSeq!(config, getoptResult) = parseCommandLine( 128 [ "dstep", "file.h", "--skip", "foo" ]); 129 assert(!config.skipSymbols.find("foo").empty); 130 131 AliasSeq!(config, getoptResult) = parseCommandLine( 132 [ "dstep", "file.h", "--skip", "foo", "--skip-definition", "bar" ]); 133 assert(!config.skipDefinitions.find("bar").empty); 134 assert(!config.skipSymbols.find("foo").empty); 135 136 AliasSeq!(config, getoptResult) = parseCommandLine( 137 [ "dstep", "foo.h", "--", "-include", "bar.h" ]); 138 assert(config.inputFiles == [ "foo.h" ]); 139 assert(config.clangParams == [ "-include", "bar.h" ]); 140 } 141 142 /** 143 * "Real" application entry point, handles CLI/config and forwards to 144 * dstep.driver.Application to do actual work. 145 * Called by main. 146 */ 147 int run(string[] args) 148 { 149 import std.stdio; 150 import std..string; 151 import clang.Util; 152 153 auto parseResult = parseCommandLine(args); 154 Configuration config = parseResult[0]; 155 GetoptResult getoptResult = parseResult[1]; 156 157 if (getoptResult.helpWanted || args.length == 1) 158 { 159 showHelp(config, getoptResult); 160 return 0; 161 } 162 163 if (config.dstepVersion) 164 { 165 writeln(strip(config.Version)); 166 return 0; 167 } 168 169 if (config.clangVersion) 170 { 171 writeln(clangVersionString()); 172 return 0; 173 } 174 175 import dstep.driver.Application; 176 177 auto application = new Application(config); 178 179 try 180 { 181 application.run(); 182 } 183 catch (DStepException e) 184 { 185 write(e.msg); 186 return -1; 187 } 188 catch (Throwable e) 189 { 190 writeln("dstep: an unknown error occurred: ", e); 191 throw e; 192 } 193 194 return 0; 195 196 } 197 198 void showHelp (Configuration config, GetoptResult getoptResult) 199 { 200 import std.stdio; 201 import std..string; 202 import std.range; 203 import std.algorithm; 204 205 struct Entry 206 { 207 this(string option, string help) 208 { 209 this.option = option; 210 this.help = help; 211 } 212 213 this(Option option) 214 { 215 if (option.optShort && option.optLong) 216 this.option = format("%s, %s", option.optShort, option.optLong); 217 else if (option.optShort) 218 this.option = option.optShort; 219 else 220 this.option = option.optLong; 221 222 auto pair = findSplitAfter(option.help, "!"); 223 224 if (!pair[0].empty) 225 { 226 this.option ~= pair[0][0 .. $ - 1]; 227 this.help = pair[1]; 228 } 229 else 230 { 231 this.help = option.help; 232 } 233 } 234 235 string option; 236 string help; 237 } 238 239 auto customEntries = [ 240 Entry("-o, --output <file>", "Write output to <file>."), 241 Entry("-o, --output <directory>", "Write all the files to <directory>, in case of multiple input files."), 242 Entry("-ObjC, --objective-c", "Treat source input file as Objective-C input."), 243 Entry("-x, --language", "Treat subsequent input files as having type <language>.")]; 244 245 auto generatedEntries = getoptResult.options 246 .filter!(option => !option.help.empty) 247 .map!(option => Entry(option)); 248 249 auto entries = chain(customEntries, generatedEntries); 250 251 auto maxLength = entries.map!(entry => entry.option.length).array.reduce!max; 252 253 auto helpString = appender!string(); 254 255 helpString.put("Usage: dstep [options] <input>\n"); 256 helpString.put(format("Version: %s\n\n", strip(config.Version))); 257 helpString.put("Options:\n"); 258 259 foreach (entry; entries) 260 helpString.put(format(" %-*s %s\n", cast(int) maxLength + 1, entry.option, entry.help)); 261 262 helpString.put( 263 "\nAll options that Clang accepts can be used as well.\n" ~ 264 "Use the `-h' flag for help."); 265 266 writeln(helpString.data); 267 }