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.ctestdouble.frontend.xml;
11 
12 import logger = std.experimental.logger;
13 
14 static import dextool.xml;
15 
16 import dextool.compilation_db;
17 import dextool.type;
18 
19 import dextool.plugin.ctestdouble.frontend.types;
20 
21 alias readRawConfig = dextool.xml.readRawConfig!(XmlConfig, parseRawConfig);
22 
23 /** Store the input in a configuration file to make it easy to regenerate the
24  * test double.
25  */
26 ref AppT makeXmlConfig(AppT)(ref AppT app, CompileCommandFilter compiler_flag_filter,
27         FilterSymbol restrict_sym, FilterSymbol exclude_sym) {
28     import std.algorithm : joiner, copy, map;
29     import std.conv : to;
30     import std.range : chain;
31     import std.xml;
32     import dextool.utility : dextoolVersion;
33     import dextool.xml : makePrelude;
34 
35     auto doc = new Document(new Tag("dextool"));
36     doc.tag.attr["version"] = dextoolVersion.get;
37     {
38         auto compiler_tag = new Element("compiler_flag_filter");
39         compiler_tag.tag.attr["skip_compiler_args"]
40             = compiler_flag_filter.skipCompilerArgs.to!string();
41         foreach (value; compiler_flag_filter.filter) {
42             auto tag = new Element("exclude");
43             tag ~= new Text(value);
44             compiler_tag ~= tag;
45         }
46         doc ~= compiler_tag;
47     }
48 
49     if (restrict_sym.hasSymbols || exclude_sym.hasSymbols) {
50         auto symbol_tag = new Element("symbol_filter");
51         foreach (value; chain(restrict_sym.range.map!((a) {
52                     auto tag = new Element("restrict");
53                     tag ~= new Text(a.key);
54                     return tag;
55                 }), exclude_sym.range.map!((a) {
56                     auto tag = new Element("exclude");
57                     tag ~= new Text(a.key);
58                     return tag;
59                 }))) {
60             symbol_tag ~= value;
61         }
62         doc ~= symbol_tag;
63     }
64 
65     makePrelude(app);
66     doc.pretty(4).joiner("\n").copy(app);
67 
68     return app;
69 }
70 
71 XmlConfig parseRawConfig(T)(T xml) @trusted {
72     import std.conv : to, ConvException;
73     import std.xml;
74 
75     DextoolVersion version_;
76     int skip_flags = 1;
77     RawCliArguments command;
78     FilterClangFlag[] filter_clang_flags;
79     FilterSymbol restrict_symbols;
80     FilterSymbol exclude_symbols;
81 
82     if (auto tag = "version" in xml.tag.attr) {
83         version_ = DextoolVersion(*tag);
84     }
85 
86     // dfmt off
87     xml.onStartTag["compiler_flag_filter"] = (ElementParser filter_flags) {
88         if (auto tag = "skip_compiler_args" in xml.tag.attr) {
89             try {
90                 skip_flags = (*tag).to!int;
91             }
92             catch (ConvException ex) {
93                 logger.info(ex.msg);
94                 logger.info("   using fallback '1'");
95             }
96         }
97 
98         xml.onEndTag["exclude"] = (const Element e) { filter_clang_flags ~= FilterClangFlag(e.text()); };
99     };
100     xml.onStartTag["symbol_filter"] = (ElementParser filter_sym) {
101         xml.onEndTag["restrict"] = (const Element e) { restrict_symbols.put(e.text()); };
102         xml.onEndTag["exclude"] = (const Element e) { exclude_symbols.put(e.text()); };
103     };
104     // dfmt on
105     xml.parse();
106 
107     return XmlConfig(version_, skip_flags, command, filter_clang_flags,
108             restrict_symbols, exclude_symbols);
109 }
110 
111 /** Extracted configuration data from an XML file.
112  *
113  * It is not inteded to be used as is but rather further processed.
114  */
115 struct XmlConfig {
116     import dextool.type : DextoolVersion, FilterClangFlag;
117 
118     DextoolVersion version_;
119     int skipCompilerArgs;
120     RawCliArguments command;
121     FilterClangFlag[] filterClangFlags;
122 
123     /// Only a symbol that matches this
124     FilterSymbol restrictSymbols;
125     /// Remove symbols matching this
126     FilterSymbol excludeSymbols;
127 }
128 
129 @("Converted a raw xml config without loosing any configuration data or version")
130 unittest {
131     import unit_threaded : shouldEqual;
132     import std.xml;
133 
134     string raw = `
135 <?xml version="1.0" encoding="UTF-8"?>
136 <dextool version="test">
137     <!-- comment is ignored -->
138     <command>tag is ignored</command>
139     <compiler_flag_filter skip_compiler_args="2">
140         <exclude>foo</exclude>
141         <exclude>-foo</exclude>
142         <exclude>--foo</exclude>
143         <exclude>-G 0</exclude>
144     </compiler_flag_filter>
145 </dextool>`;
146 
147     auto xml = new DocumentParser(raw);
148     auto p = parseRawConfig(xml);
149 
150     p.version_.get.shouldEqual("test");
151     p.command.payload.shouldEqual(string[].init);
152     p.skipCompilerArgs.shouldEqual(2);
153     p.filterClangFlags.shouldEqual([
154             FilterClangFlag("foo"), FilterClangFlag("-foo"),
155             FilterClangFlag("--foo"), FilterClangFlag("-G 0")
156             ]);
157 }
158 
159 /// The raw arguments from the command line.
160 struct RawCliArguments {
161     string[] payload;
162     alias payload this;
163 }