1 /** 2 Copyright: Copyright (c) 2017, Joakim Brännström. All rights reserved. 3 License: MPL-2 4 Author: Joakim Brännström (joakim.brannstrom@gmx.com) 5 6 This Source Code Form is subject to the terms of the Mozilla Public License, 7 v.2.0. If a copy of the MPL was not distributed with this file, You can obtain 8 one at http://mozilla.org/MPL/2.0/. 9 */ 10 module dextool.plugin.cpptestdouble.frontend.raw_args; 11 12 import std.typecons : Nullable; 13 14 import logger = std.experimental.logger; 15 16 static import dextool.xml; 17 18 /// Represent a yes/no configuration option. 19 /// Using an explicit name so the help text is improved in such a way that the 20 /// user understand that the choices are between yes/no. 21 enum Config_YesNo { 22 no, 23 yes 24 } 25 26 struct RawConfiguration { 27 import std.conv : ConvException; 28 import std.getopt : GetoptResult, getopt, defaultGetoptPrinter; 29 import dextool.type : Path; 30 31 Nullable!XmlConfig xmlConfig; 32 33 Config_YesNo gtestPODPrettyPrint = Config_YesNo.yes; 34 Path[] inFiles; 35 bool doFreeFuncs; 36 bool genPostInclude; 37 bool generatePreInclude; 38 bool gmock; 39 bool help; 40 bool shortPluginHelp; 41 string config; 42 string header; 43 string headerFile; 44 string mainFileName = "test_double"; 45 string mainName = "TestDouble"; 46 string out_; 47 string prefix = "Test_"; 48 string stripInclude; 49 string systemCompiler; 50 string[] cflags; 51 string[] compileDb; 52 string[] fileExclude; 53 string[] fileInclude; 54 string[] testDoubleInclude; 55 56 string[] originalFlags; 57 58 private GetoptResult help_info; 59 60 void parse(string[] args) { 61 import std.traits : EnumMembers; 62 import std.format : format; 63 64 static import std.getopt; 65 66 originalFlags = args.dup; 67 string[] input; 68 69 try { 70 // dfmt off 71 // sort alphabetic 72 help_info = getopt(args, std.getopt.config.keepEndOfOptions, 73 "compile-db", "Retrieve compilation parameters from the file", &compileDb, 74 "config", "Use configuration file", &config, 75 "file-exclude", "Exclude files from generation matching the regex", &fileExclude, 76 "file-include", "Include the scope of the test double to those files matching the regex.", &fileInclude, 77 "free-func", "Generate test doubles of free functions", &doFreeFuncs, 78 "gen-post-incl", "Generate a post include header file if it doesn't exist and use it", &genPostInclude, 79 "gen-pre-incl", "Generate a pre include header file if it doesn't exist and use it", &generatePreInclude, 80 "gmock", "Generate a gmock implementation of test double interface", &gmock, 81 "gtest-pp", "Generate pretty printer of POD's public members for gtest " ~ format("[%(%s|%)]", [EnumMembers!Config_YesNo]), >estPODPrettyPrint, 82 "header", "Prepends generated files with the string", &header, 83 "header-file", "Prepend generated files with the header read from the file", &headerFile, 84 "in", "Input file to parse (at least one)", &input, 85 "main", "Used as part of interface, namespace etc [default: TestDouble]", &mainName, 86 "main-fname", "Used as part of filename for generated files [default: test_double]", &mainFileName, 87 "out", "directory for generated files [default: ./]", &out_, 88 "prefix", "Prefix used when generating test artifacts [default: Test_]", &prefix, 89 "short-plugin-help", "short description of the plugin", &shortPluginHelp, 90 "strip-incl", "A regex used to strip the include paths", &stripInclude, 91 "system-compiler", "Derive the system include paths from this compiler [default: use from compile_commands.json]", &systemCompiler, 92 "td-include", "User supplied includes used instead of those found", &testDoubleInclude, 93 ); 94 // dfmt on 95 help = help_info.helpWanted; 96 } catch (ConvException e) { 97 logger.error(e.msg); 98 logger.errorf("%s possible values: %(%s|%)", Config_YesNo.stringof, 99 [EnumMembers!Config_YesNo]); 100 help = true; 101 } catch (std.getopt.GetOptException ex) { 102 logger.error(ex.msg); 103 help = true; 104 } catch (Exception ex) { 105 logger.error(ex.msg); 106 help = true; 107 } 108 109 // default arguments 110 if (stripInclude.length == 0) { 111 stripInclude = r".*/(.*)"; 112 } 113 114 if (config.length != 0) { 115 xmlConfig = readRawConfig(Path(config)); 116 if (xmlConfig.isNull) { 117 help = true; 118 } 119 } 120 121 import std.algorithm : find, map; 122 import std.array : array; 123 import std.range : drop; 124 125 inFiles = input.map!(a => Path(a)).array; 126 127 // at this point args contain "what is left". What is interesting then is those after "--". 128 cflags = args.find("--").drop(1).array(); 129 } 130 131 void printHelp() { 132 import std.stdio : writeln; 133 134 defaultGetoptPrinter("Usage: dextool cpptestdouble [options] [--in=] [-- CFLAGS...]", 135 help_info.options); 136 137 writeln(" 138 REGEX 139 The regex syntax is found at http://dlang.org/phobos/std_regex.html 140 141 Information about --strip-incl. 142 Default regexp is: .*/(.*) 143 144 To allow the user to selectively extract parts of the include path dextool 145 applies the regex and then concatenates all the matcher groups found. It is 146 turned into the replacement include path. 147 148 Important to remember then is that this approach requires that at least one 149 matcher group exists. 150 151 Information about --file-exclude. 152 The regex must fully match the filename the AST node is located in. 153 If it matches all data from the file is excluded from the generated code. 154 155 Information about --file-include. 156 The regex must fully match the filename the AST node is located in. 157 Only symbols from files matching the include affect the generated test double. 158 159 Information about --in. 160 When it is used in conjuction with a compile commands database it is used to also find the flags. 161 For each entry in the database the argument to --in is matched against the file or output JSON attribute. 162 If either of them match the compiler flags for that file are used. 163 The argument can be either the absolute path, the exact file or output attribute or a file glob pattern. 164 A glob pattern is such as `ls *.cpp`. 165 "); 166 } 167 } 168 169 /** Extracted configuration data from an XML file. 170 * 171 * It is not inteded to be used as is but rather further processed. 172 */ 173 struct XmlConfig { 174 import dextool.type : DextoolVersion, FilterClangFlag; 175 176 DextoolVersion version_; 177 int skipCompilerArgs; 178 RawCliArguments command; 179 FilterClangFlag[] filterClangFlags; 180 } 181 182 auto parseRawConfig(T)(T xml) @trusted { 183 import std.conv : to, ConvException; 184 import std.xml; 185 import dextool.utility : DextoolVersion; 186 import dextool.type : FilterClangFlag; 187 188 DextoolVersion version_; 189 int skip_flags = 1; 190 RawCliArguments command; 191 FilterClangFlag[] filter_clang_flags; 192 193 if (auto tag = "version" in xml.tag.attr) { 194 version_ = DextoolVersion(*tag); 195 } 196 197 // dfmt off 198 xml.onStartTag["compiler_flag_filter"] = (ElementParser filter_flags) { 199 if (auto tag = "skip_compiler_args" in xml.tag.attr) { 200 try { 201 skip_flags = (*tag).to!int; 202 } 203 catch (ConvException ex) { 204 logger.info(ex.msg); 205 logger.info(" using fallback '1'"); 206 } 207 } 208 209 xml.onEndTag["exclude"] = (const Element e) { filter_clang_flags ~= FilterClangFlag(e.text()); }; 210 }; 211 // dfmt on 212 xml.parse(); 213 214 return XmlConfig(version_, skip_flags, command, filter_clang_flags); 215 } 216 217 alias readRawConfig = dextool.xml.readRawConfig!(XmlConfig, parseRawConfig); 218 219 /// The raw arguments from the command line. 220 struct RawCliArguments { 221 string[] payload; 222 alias payload this; 223 }