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 : FileName; 30 31 Nullable!XmlConfig xmlConfig; 32 33 string[] fileExclude; 34 string[] fileRestrict; 35 string[] testDoubleInclude; 36 string[] inFiles; 37 string[] cflags; 38 string[] compileDb; 39 string header; 40 string headerFile; 41 string mainName = "TestDouble"; 42 string mainFileName = "test_double"; 43 string prefix = "Test_"; 44 string stripInclude; 45 string out_; 46 string config; 47 bool doFreeFuncs; 48 bool shortPluginHelp; 49 bool help; 50 bool gmock; 51 Config_YesNo gtestPODPrettyPrint = Config_YesNo.yes; 52 bool generatePreInclude; 53 bool genPostInclude; 54 55 string[] originalFlags; 56 57 private GetoptResult help_info; 58 59 void parse(string[] args) { 60 import std.traits : EnumMembers; 61 import std.format : format; 62 63 static import std.getopt; 64 65 originalFlags = args.dup; 66 67 try { 68 // dfmt off 69 // sort alphabetic 70 help_info = getopt(args, std.getopt.config.keepEndOfOptions, 71 "compile-db", "Retrieve compilation parameters from the file", &compileDb, 72 "config", "Use configuration file", &config, 73 "free-func", "Generate test doubles of free functions", &doFreeFuncs, 74 "file-exclude", "Exclude files from generation matching the regex", &fileExclude, 75 "file-restrict", "Restrict the scope of the test double to those files matching the regex.", &fileRestrict, 76 "gmock", "Generate a gmock implementation of test double interface", &gmock, 77 "gtest-pp", "Generate pretty printer of POD's public members for gtest " ~ format("[%(%s|%)]", [EnumMembers!Config_YesNo]), >estPODPrettyPrint, 78 "gen-pre-incl", "Generate a pre include header file if it doesn't exist and use it", &generatePreInclude, 79 "gen-post-incl", "Generate a post include header file if it doesn't exist and use it", &genPostInclude, 80 "header", "Prepends generated files with the string", &header, 81 "header-file", "Prepend generated files with the header read from the file", &headerFile, 82 "in", "Input file to parse (at least one)", &inFiles, 83 "main", "Used as part of interface, namespace etc [default: TestDouble]", &mainName, 84 "main-fname", "Used as part of filename for generated files [default: test_double]", &mainFileName, 85 "out", "directory for generated files [default: ./]", &out_, 86 "prefix", "Prefix used when generating test artifacts [default: Test_]", &prefix, 87 "short-plugin-help", "short description of the plugin", &shortPluginHelp, 88 "strip-incl", "A regex used to strip the include paths", &stripInclude, 89 "td-include", "User supplied includes used instead of those found", &testDoubleInclude, 90 ); 91 // dfmt on 92 help = help_info.helpWanted; 93 } 94 catch (ConvException e) { 95 logger.error(e.msg); 96 logger.errorf("%s possible values: %(%s|%)", Config_YesNo.stringof, 97 [EnumMembers!Config_YesNo]); 98 help = true; 99 } 100 catch (std.getopt.GetOptException ex) { 101 logger.error(ex.msg); 102 help = true; 103 } 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(FileName(config)); 116 if (xmlConfig.isNull) { 117 help = true; 118 } 119 } 120 121 import std.algorithm : find; 122 import std.array : array; 123 import std.range : drop; 124 125 // at this point args contain "what is left". What is interesting then is those after "--". 126 cflags = args.find("--").drop(1).array(); 127 } 128 129 void printHelp() { 130 import std.stdio : writeln; 131 132 defaultGetoptPrinter("Usage: dextool cpptestdouble [options] [--in=] [-- CFLAGS...]", 133 help_info.options); 134 135 writeln(" 136 REGEX 137 The regex syntax is found at http://dlang.org/phobos/std_regex.html 138 139 Information about --strip-incl. 140 Default regexp is: .*/(.*) 141 142 To allow the user to selectively extract parts of the include path dextool 143 applies the regex and then concatenates all the matcher groups found. It is 144 turned into the replacement include path. 145 146 Important to remember then is that this approach requires that at least one 147 matcher group exists. 148 149 Information about --file-exclude. 150 The regex must fully match the filename the AST node is located in. 151 If it matches all data from the file is excluded from the generated code. 152 153 Information about --file-restrict. 154 The regex must fully match the filename the AST node is located in. 155 Only symbols from files matching the restrict affect the generated test double. 156 157 Information about --in. 158 When it is used in conjuction with a compile commands database it is used to also find the flags. 159 For each entry in the database the argument to --in is matched against the file or output JSON attribute. 160 If either of them match the compiler flags for that file are used. 161 The argument can be either the absolute path, the exact file or output attribute or a file glob pattern. 162 A glob pattern is such as `ls *.cpp`. 163 "); 164 } 165 } 166 167 /** Extracted configuration data from an XML file. 168 * 169 * It is not inteded to be used as is but rather further processed. 170 */ 171 struct XmlConfig { 172 import dextool.type : DextoolVersion, RawCliArguments, FilterClangFlag; 173 174 DextoolVersion version_; 175 int skipCompilerArgs; 176 RawCliArguments command; 177 FilterClangFlag[] filterClangFlags; 178 } 179 180 auto parseRawConfig(T)(T xml) @trusted { 181 import std.conv : to, ConvException; 182 import std.xml; 183 import dextool.utility : DextoolVersion; 184 import dextool.type : RawCliArguments, FilterClangFlag; 185 186 DextoolVersion version_; 187 int skip_flags = 1; 188 RawCliArguments command; 189 FilterClangFlag[] filter_clang_flags; 190 191 if (auto tag = "version" in xml.tag.attr) { 192 version_ = *tag; 193 } 194 195 // dfmt off 196 xml.onStartTag["compiler_flag_filter"] = (ElementParser filter_flags) { 197 if (auto tag = "skip_compiler_args" in xml.tag.attr) { 198 try { 199 skip_flags = (*tag).to!int; 200 } 201 catch (ConvException ex) { 202 logger.info(ex.msg); 203 logger.info(" using fallback '1'"); 204 } 205 } 206 207 xml.onEndTag["exclude"] = (const Element e) { filter_clang_flags ~= FilterClangFlag(e.text()); }; 208 }; 209 // dfmt on 210 xml.parse(); 211 212 return XmlConfig(version_, skip_flags, command, filter_clang_flags); 213 } 214 215 alias readRawConfig = dextool.xml.readRawConfig!(XmlConfig, parseRawConfig);