1 /++ Ponies main module.
2  +
3  + <img src="images/dependencies.svg" />
4  +
5  + Authors: Christian Koestlin, Christian Köstlin
6  + Copyright: Copyright (c) 2018, Christian Koestlin
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", "text")(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.text);
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 string[] doctor()
183     {
184         return [];
185     }
186 
187     public abstract void run();
188 }
189 
190 alias UserAndProject = Tuple!(string, "user", string, "project");
191 UserAndProject userAndProject;
192 auto getUserAndProject()
193 {
194     import std.process;
195     import std.string : replace;
196 
197     auto res = ["git", "remote", "get-url", "origin"].execute;
198     auto pattern = "github.com:(?P<user>.*)/(?P<project>.*)";
199     auto match = matchFirst(res.output, regex(pattern, "m"));
200     if (match)
201     {
202         return UserAndProject(match["user"], match["project"].replace(".git", ""));
203     }
204     else
205     {
206         return UserAndProject(cast(string) null, cast(string) null);
207     }
208 }