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 } catch (ConvException e) { 94 logger.error(e.msg); 95 logger.errorf("%s possible values: %(%s|%)", Config_YesNo.stringof, 96 [EnumMembers!Config_YesNo]); 97 help = true; 98 } catch (std.getopt.GetOptException ex) { 99 logger.error(ex.msg); 100 help = true; 101 } catch (Exception ex) { 102 logger.error(ex.msg); 103 help = true; 104 } 105 106 // default arguments 107 if (stripInclude.length == 0) { 108 stripInclude = r".*/(.*)"; 109 } 110 111 if (config.length != 0) { 112 xmlConfig = readRawConfig(FileName(config)); 113 if (xmlConfig.isNull) { 114 help = true; 115 } 116 } 117 118 import std.algorithm : find; 119 import std.array : array; 120 import std.range : drop; 121 122 // at this point args contain "what is left". What is interesting then is those after "--". 123 cflags = args.find("--").drop(1).array(); 124 } 125 126 void printHelp() { 127 import std.stdio : writeln; 128 129 defaultGetoptPrinter("Usage: dextool cpptestdouble [options] [--in=] [-- CFLAGS...]", 130 help_info.options); 131 132 writeln(" 133 REGEX 134 The regex syntax is found at http://dlang.org/phobos/std_regex.html 135 136 Information about --strip-incl. 137 Default regexp is: .*/(.*) 138 139 To allow the user to selectively extract parts of the include path dextool 140 applies the regex and then concatenates all the matcher groups found. It is 141 turned into the replacement include path. 142 143 Important to remember then is that this approach requires that at least one 144 matcher group exists. 145 146 Information about --file-exclude. 147 The regex must fully match the filename the AST node is located in. 148 If it matches all data from the file is excluded from the generated code. 149 150 Information about --file-restrict. 151 The regex must fully match the filename the AST node is located in. 152 Only symbols from files matching the restrict affect the generated test double. 153 154 Information about --in. 155 When it is used in conjuction with a compile commands database it is used to also find the flags. 156 For each entry in the database the argument to --in is matched against the file or output JSON attribute. 157 If either of them match the compiler flags for that file are used. 158 The argument can be either the absolute path, the exact file or output attribute or a file glob pattern. 159 A glob pattern is such as `ls *.cpp`. 160 "); 161 } 162 } 163 164 /** Extracted configuration data from an XML file. 165 * 166 * It is not inteded to be used as is but rather further processed. 167 */ 168 struct XmlConfig { 169 import dextool.type : DextoolVersion, RawCliArguments, FilterClangFlag; 170 171 DextoolVersion version_; 172 int skipCompilerArgs; 173 RawCliArguments command; 174 FilterClangFlag[] filterClangFlags; 175 } 176 177 auto parseRawConfig(T)(T xml) @trusted { 178 import std.conv : to, ConvException; 179 import std.xml; 180 import dextool.utility : DextoolVersion; 181 import dextool.type : RawCliArguments, FilterClangFlag; 182 183 DextoolVersion version_; 184 int skip_flags = 1; 185 RawCliArguments command; 186 FilterClangFlag[] filter_clang_flags; 187 188 if (auto tag = "version" in xml.tag.attr) { 189 version_ = *tag; 190 } 191 192 // dfmt off 193 xml.onStartTag["compiler_flag_filter"] = (ElementParser filter_flags) { 194 if (auto tag = "skip_compiler_args" in xml.tag.attr) { 195 try { 196 skip_flags = (*tag).to!int; 197 } 198 catch (ConvException ex) { 199 logger.info(ex.msg); 200 logger.info(" using fallback '1'"); 201 } 202 } 203 204 xml.onEndTag["exclude"] = (const Element e) { filter_clang_flags ~= FilterClangFlag(e.text()); }; 205 }; 206 // dfmt on 207 xml.parse(); 208 209 return XmlConfig(version_, skip_flags, command, filter_clang_flags); 210 } 211 212 alias readRawConfig = dextool.xml.readRawConfig!(XmlConfig, parseRawConfig);