1 /++ Ponies main module.
2  +
3  + <img src="images/dependencies.svg" />
4  +
5  + Authors: Christian Koestlin
6  + Copyright: Copyright © 2018, Christian Köstlin
7  + License: MIT
8  +/
9 module ponies;
10 
11 public import ponies.packageversion;
12 
13 import std.algorithm;
14 import std.conv;
15 import std.experimental.logger;
16 import std.file;
17 import std.functional;
18 import std.range;
19 import std.regex;
20 import std.stdio;
21 import std..string;
22 import std.traits;
23 import std.typecons;
24 
25 enum Vote
26 {
27     up,
28     down,
29     dontCare
30 }
31 
32 auto removePlusMinusPrefix(string s)
33 {
34     auto negate = false;
35     if (s[0] == '-')
36     {
37         negate = true;
38         s = s[1 .. $];
39     }
40     else if (s[0] == '+')
41     {
42         s = s[1 .. $];
43     }
44     return tuple!("negative", "string")(negate, s);
45 }
46 
47 @("check remove plusminusprefix") unittest
48 {
49     import unit_threaded;
50 
51     "-test".removePlusMinusPrefix.shouldEqual(tuple(true, "test"));
52     "test".removePlusMinusPrefix.shouldEqual(tuple(false, "test"));
53     "+test".removePlusMinusPrefix.shouldEqual(tuple(false, "test"));
54 }
55 
56 auto voteByPlusMinusRegex(string pony, string plusMinusRegex)
57 {
58     if (plusMinusRegex.length == 0)
59     {
60         return Vote.dontCare;
61     }
62 
63     auto pm = removePlusMinusPrefix(plusMinusRegex);
64 
65     auto r = regex(pm..string);
66     if (pony.match(r))
67     {
68         if (pm.negative)
69         {
70             return Vote.down;
71         }
72         else
73         {
74             return Vote.up;
75         }
76     }
77     else
78     {
79         return Vote.dontCare;
80     }
81 }
82 
83 bool vote(P)(P pony, bool old, string pattern)
84 {
85     auto h = voteByPlusMinusRegex(pony.to!string, pattern);
86     switch (h)
87     {
88     case Vote.up:
89         return true;
90     case Vote.down:
91         return false;
92     default:
93         return old;
94     }
95 }
96 
97 bool selected(P)(P pony, string what)
98 {
99     // dfmt off
100     return what
101         .split(",")
102         .fold!((result, pattern) => pony.vote(result, pattern))
103         (false);
104     // dfmt on
105 }
106 
107 @("select a pony") unittest
108 {
109     import unit_threaded;
110 
111     "test".selected(".*").shouldBeTrue;
112     "test".selected("-.*").shouldBeFalse;
113     "test".selected("test1,+test").shouldBeTrue;
114     "test".selected("test1,-test").shouldBeFalse;
115     "test".selected(".*,-test").shouldBeFalse;
116     "test".selected("-.*,+test").shouldBeTrue;
117     "a.dlang.pony".selected(".*dlang.*").shouldBeTrue;
118     "a.dlang.pony".selected("-.*dlang.*").shouldBeFalse;
119 }
120 
121 auto readyToRun(P)(P ponies)
122 {
123     return ponies.filter!(a => a.applicable).array;
124 }
125 
126 auto poniesToRun(P)(P ponies, string what)
127 {
128     return ponies.readyToRun.filter!(a => a.selected(what));
129 }
130 
131 enum What
132 {
133     all,
134     readyToRun
135 }
136 
137 auto select(T)(T ponies, What what)
138 {
139     switch (what)
140     {
141     case What.all:
142         return ponies;
143     case What.readyToRun:
144         return ponies.readyToRun;
145     default:
146         throw new Exception("nyi");
147     }
148 }
149 
150 auto possibleValues(E)() if (is(E == enum))
151 {
152     return [EnumMembers!E].map!("a.to!string").join(", ");
153 }
154 
155 T askFor(T)() if (is(T == enum))
156 {
157     writeln("Please enter %s [%s]: ".format(T.stringof, possibleValues!T));
158     auto line = readln.strip;
159     return line.to!T;
160 }
161 
162 enum CheckStatus
163 {
164     todo,
165     done,
166     dont_know
167 }
168 
169 @("bool to checkstatus") unittest
170 {
171     import unit_threaded;
172 
173     true.to!CheckStatus.shouldEqual(CheckStatus.done);
174     false.to!CheckStatus.shouldEqual(CheckStatus.todo);
175 }
176 
177 abstract class Pony
178 {
179     public abstract string name();
180     public abstract bool applicable();
181     public abstract CheckStatus check();
182     public abstract void run();
183 }
184 
185 alias UserAndProject = Tuple!(string, "user", string, "project");
186 UserAndProject userAndProject;
187 auto getUserAndProject()
188 {
189     import std.process;
190     import std..string : replace;
191 
192     auto res = ["git", "remote", "get-url", "origin"].execute;
193     auto pattern = "github.com:(?P<user>.*)/(?P<project>.*)";
194     auto match = matchFirst(res.output, regex(pattern, "m"));
195     if (match)
196     {
197         return UserAndProject(match["user"], match["project"].replace(".git", ""));
198     }
199     else
200     {
201         return UserAndProject(cast(string) null, cast(string) null);
202     }
203 }
204 
205 class ShieldPony : Pony
206 {
207     protected UserAndProject userAndProject;
208     this()
209     {
210         userAndProject = getUserAndProject;
211     }
212 
213     override bool applicable()
214     {
215         return exists("readme.org") && userAndProject.user != null && userAndProject.project != null;
216     }
217 
218     override CheckStatus check()
219     {
220         return readText("readme.org").canFind(shield.strip).to!CheckStatus;
221     }
222 
223     abstract string shield();
224 
225     override void run()
226     {
227         "Please resort your readme.org to put the shield to the right place".warning;
228         append("readme.org", shield);
229     }
230 }
231 
232 class GithubShieldPony : ShieldPony
233 {
234     override string name()
235     {
236         return "Setup a link to github in readme.org";
237     }
238 
239     override string shield()
240     {
241         return "[[https://github.com/%1$s/%2$s][https://img.shields.io/github/tag/%1$s/%2$s.svg?style=flat-square]]\n"
242             .format(userAndProject.user, userAndProject.project);
243     }
244 }
245 
246 class CodecovShieldPony : ShieldPony
247 {
248     override string name()
249     {
250         return "Setup a link to codecov in readme.org";
251     }
252 
253     override string shield()
254     {
255         return "[[https://codecov.io/gh/%1$s/%2$s][https://img.shields.io/codecov/c/github/%1$s/%2$s/master.svg?style=flat-square]]\n"
256             .format(userAndProject.user, userAndProject.project);
257     }
258 }
259 
260 class TravisCiShieldPony : ShieldPony
261 {
262     override string name()
263     {
264         return "Setup a travis ci shield in readme.org";
265     }
266 
267     override string shield()
268     {
269         return "[[https://travis-ci.org/%1$s/%2$s][https://img.shields.io/travis/%1$s/%2$s/master.svg?style=flat-square]]\n"
270             .format(userAndProject.user, userAndProject.project);
271     }
272 }
273 
274 class GithubPagesShieldPony : ShieldPony
275 {
276     override string name()
277     {
278         return "Setup a documentation shield in readme.org";
279     }
280 
281     override string shield()
282     {
283         return "[[https://%s.github.io/%s][https://img.shields.io/readthedocs/pip.svg?style=flat-square]]\n".format(
284                 userAndProject.user, userAndProject.project);
285     }
286 }