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 }