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