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