1 /++ Ponies main module. 2 + 3 + <img src="images/dependencies.svg" /> 4 + 5 + Authors: Christian Koestlin, Christian Köstlin 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", "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 } 209 210 class ShieldPony : Pony 211 { 212 protected UserAndProject userAndProject; 213 this() 214 { 215 userAndProject = getUserAndProject; 216 } 217 218 override bool applicable() 219 { 220 return exists("readme.org") && userAndProject.user != null && userAndProject.project != null; 221 } 222 223 override CheckStatus check() 224 { 225 return readText("readme.org").canFind(shield.strip).to!CheckStatus; 226 } 227 228 override string[] doctor() 229 { 230 if (!exists("readme.org")) 231 { 232 return ["Please add readme.org"]; 233 } 234 return []; 235 } 236 237 abstract string shield(); 238 239 override void run() 240 { 241 "Please resort your readme.org to put the shield to the right place".warning; 242 append("readme.org", shield); 243 } 244 } 245 246 class GithubShieldPony : ShieldPony 247 { 248 override string name() 249 { 250 return "Setup a link to github in readme.org"; 251 } 252 253 override string shield() 254 { 255 return "[[https://github.com/%1$s/%2$s][https://img.shields.io/github/tag/%1$s/%2$s.svg?style=flat-square]]\n" 256 .format(userAndProject.user, userAndProject.project); 257 } 258 } 259 260 class CodecovShieldPony : ShieldPony 261 { 262 override string name() 263 { 264 return "Setup a link to codecov in readme.org"; 265 } 266 267 override string shield() 268 { 269 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" 270 .format(userAndProject.user, userAndProject.project); 271 } 272 } 273 274 class TravisCiShieldPony : ShieldPony 275 { 276 override string name() 277 { 278 return "Setup a travis ci shield in readme.org"; 279 } 280 281 override string shield() 282 { 283 return "[[https://travis-ci.org/%1$s/%2$s][https://img.shields.io/travis/%1$s/%2$s/master.svg?style=flat-square]]\n" 284 .format(userAndProject.user, userAndProject.project); 285 } 286 } 287 288 class GithubPagesShieldPony : ShieldPony 289 { 290 override string name() 291 { 292 return "Setup a documentation shield in readme.org"; 293 } 294 295 override string shield() 296 { 297 return "[[https://%s.github.io/%s][https://img.shields.io/readthedocs/pip.svg?style=flat-square]]\n".format( 298 userAndProject.user, userAndProject.project); 299 } 300 } 301 302 bool mightBeEmacs() 303 { 304 return exists("Cask"); 305 } 306 307 class MelpaShieldPony : ShieldPony 308 { 309 protected UserAndProject userAndProject; 310 311 this() 312 { 313 userAndProject = getUserAndProject; 314 } 315 316 override string name() 317 { 318 return "Setup a melpa shield in readme.org"; 319 } 320 321 override bool applicable() 322 { 323 return super.applicable() && mightBeEmacs; 324 } 325 326 override string shield() 327 { 328 return "[[https://melpa.org/#/%1$s][https://melpa.org/packages/%1$s-badge.svg]]".format( 329 userAndProject.project); 330 } 331 }