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 autoformat.format_c_cpp;
11 
12 import std.algorithm;
13 import std.array;
14 import std.exception;
15 import std.process;
16 import std.typecons : Flag;
17 
18 import logger = std.experimental.logger;
19 
20 import autoformat.types;
21 
22 private immutable string[] astyleConf = import("astyle.conf").splitter("\n")
23     .filter!(a => a.length > 0).array ~ ["-Q"];
24 
25 private immutable string[] clangFormatConf = import("clang_format.conf").splitter(
26         "\n").filter!(a => a.length != 0).array;
27 
28 // Thread local optimization that reduced the console spam when the program
29 // isn't installed.
30 private bool installed = true;
31 
32 immutable clangToolEnvKey = "AUTOFORMAT_CLANG_TOOL";
33 
34 auto getClangFormatterTool() @safe nothrow {
35     try {
36         return environment.get(clangToolEnvKey, "clang-format");
37     } catch (Exception e) {
38     }
39     return "astyle";
40 }
41 
42 auto runClangFormatter(AbsolutePath fname, Flag!"backup" backup, Flag!"dryRun" dry_run) nothrow {
43     string tool = getClangFormatterTool;
44     return runClangFormat(fname, backup, dry_run);
45 }
46 
47 auto runClangFormat(AbsolutePath fname, Flag!"backup" backup, Flag!"dryRun" dry_run) nothrow {
48     import std.file : copy;
49 
50     string[] opts = clangFormatConf.map!(a => a.idup).array;
51 
52     if (!installed) {
53         return FormatterResult(Unchanged.init);
54     }
55 
56     if (dry_run)
57         opts ~= "-output-replacements-xml";
58     else
59         opts ~= "-i";
60 
61     auto arg = ["clang-format"] ~ opts ~ [cast(string) fname];
62 
63     auto rval = FormatterResult(FormatError.init);
64 
65     try {
66         if (!dry_run && backup) {
67             copy(fname, fname.toString ~ ".orig");
68         }
69 
70         auto res = loggedExecute(arg);
71 
72         if (dry_run && res.output.splitter("\n").count > 3) {
73             rval = FormatterResult(WouldChange.init);
74         } else {
75             rval = FormatterResult(FormattedOk.init);
76         }
77     } catch (ProcessException e) {
78         // clang-format isn't installed
79         rval = FormatterResult(FailedWithUserMsg(e.msg));
80         installed = false;
81     } catch (Exception e) {
82         rval = FormatterResult(FailedWithUserMsg(e.msg));
83     }
84 
85     return rval;
86 }
87 
88 auto loggedExecute(string[] arg) {
89     logger.trace(arg.join(" "));
90     auto res = execute(arg);
91     logger.trace(res.output);
92     return res;
93 }