1 /**
2 Date: 2015-2017, Joakim Brännström
3 License: MPL-2, Mozilla Public License 2.0
4 Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 */
6 module cpptooling.generator.includes;
7 
8 import dsrcgen.cpp : CppModule;
9 import dextool.type : DextoolVersion, FileName, CustomHeader;
10 
11 @safe:
12 
13 /** Include headers as if they are C code.
14  *
15  * Wrapped in extern "C" to ensure C binding of the includes.
16  */
17 void generateWrapIncludeInExternC(ControllerT, ParamT)(ControllerT ctrl, ParamT params,
18         CppModule hdr) {
19     import std.path : baseName;
20 
21     if (ctrl.doIncludeOfPreIncludes) {
22         hdr.include(params.getFiles.pre_incl.baseName);
23     }
24 
25     auto extern_c = hdr.suite("extern \"C\"");
26     extern_c.suppressIndent(1);
27 
28     foreach (incl; params.getIncludes) {
29         extern_c.include(cast(string) incl);
30     }
31 
32     if (ctrl.doIncludeOfPostIncludes) {
33         hdr.include(params.getFiles.post_incl.baseName);
34     }
35 
36     hdr.sep(2);
37 }
38 
39 /** Normal, unmodified include directives.
40  *
41  * Compared to generateC there are no special wrapping extern "C" wrapping.
42  */
43 void generateIncludes(IncludesT)(IncludesT incls, CppModule hdr) {
44     foreach (incl; incls) {
45         hdr.include(cast(string) incl);
46     }
47 
48     hdr.sep(2);
49 }
50 
51 string convToIncludeGuard(FileT)(FileT fname) {
52     import std..string : translate;
53     import std.path : baseName;
54 
55     // dfmt off
56     dchar[dchar] table = [
57         '.' : '_',
58         '-' : '_',
59         '/' : '_'];
60     // dfmt on
61 
62     return translate((cast(string) fname).baseName, table);
63 }
64 
65 auto generatePreInclude(FileT)(FileT fname) {
66     import dsrcgen.cpp : CppHModule;
67 
68     auto o = CppHModule(convToIncludeGuard(fname));
69     auto c = new CppModule;
70     c.stmt("#undef __cplusplus")[$.end = ""];
71     o.content.append(c);
72 
73     return o;
74 }
75 
76 auto generatePostInclude(FileT)(FileT fname) {
77     import dsrcgen.cpp : CppHModule;
78 
79     auto o = CppHModule(convToIncludeGuard(fname));
80     auto c = new CppModule;
81     c.define("__cplusplus");
82     o.content.append(c);
83 
84     return o;
85 }
86 
87 /** Create a header.
88  *
89  * The users custom header will be parsed for the magic keywords.
90  * $file$ = replaced by the filename.
91  *
92  * Params:
93  *  fname = destination filename
94  *  ver = version of dextool
95  *  custom = header appended last, intened for user customisation
96  */
97 auto makeHeader(FileName fname, DextoolVersion ver, CustomHeader custom = CustomHeader("")) {
98     import std.algorithm : splitter, map, joiner, copy;
99     import std.array : appender;
100     import std.ascii : newline;
101     import std.path : baseName;
102     import dsrcgen.cpp : CppModule;
103 
104     auto base_fname = fname.baseName;
105     immutable string[string] kw = ["$file$" : base_fname, "$dextool_version$" : ver];
106 
107     auto m = new CppModule;
108     if (custom.length > 0) {
109         auto app = appender!string();
110         // dfmt off
111         (cast(string) custom)
112             .splitter(newline)
113             .map!(a => a
114                   .splitter(' ')
115                   .map!((word) {
116                         if (auto w = word in kw) return *w;
117                         else return word;
118                         })
119                   .map!(a => a.dup)
120                   .joiner(" ")
121                  )
122             .joiner(newline)
123             .copy(app);
124         // dfmt on
125 
126         m.text(app.data);
127         m.sep;
128     } else {
129         m.comment("@file " ~ base_fname)[$.begin = "/// "];
130         m.comment("@brief Generated by DEXTOOL_VERSION: " ~ ver)[$.begin = "/// "];
131     }
132     m.comment("DO NOT EDIT THIS FILE, it will be overwritten on update.")[$.begin = "/// "];
133 
134     return m;
135 }
136 
137 version (unittest) {
138     import unit_threaded : Values;
139 
140     @("The keyword enclosed in $...$ in the custom header is replaced with expected value")
141     @Values([["$file$", "a.h"], ["$dextool_version$", "1.0"]])
142     unittest {
143         import unit_threaded : shouldEqual, getValue;
144 
145         auto filename = FileName("a.h");
146         auto custom_header = CustomHeader("// " ~ getValue!(string[])[0]);
147         auto version_ = DextoolVersion("1.0");
148 
149         makeHeader(filename, version_, custom_header).render.shouldEqual("// " ~ getValue!(
150                 string[])[1]
151                 ~ "\n    /// DO NOT EDIT THIS FILE, it will be overwritten on update.\n");
152     }
153 }