1 /**
2 	Copyright: © 2013 rejectedsoftware e.K.
3 	License: Subject to the terms of the GNU GPLv3 license, as written in the included LICENSE.txt file.
4 	Authors: Sönke Ludwig
5 */
6 module dubregistry.viewutils;
7 
8 import std.datetime;
9 import std..string;
10 import vibe.data.json;
11 
12 string formatDate(Json date)
13 {
14 	return formatDate(date.opt!string);
15 }
16 
17 string formatDate(string iso_ext_date)
18 {
19 	if (iso_ext_date.length == 0) return "---";
20 	return formatDate(SysTime.fromISOExtString(iso_ext_date));
21 }
22 
23 string formatDate(SysTime st)
24 {
25 	return (cast(Date)st).toSimpleString();
26 }
27 
28 string formatDateTime(Json dateTime)
29 {
30 	return formatDateTime(dateTime.opt!string);
31 }
32 
33 string formatDateTime(string iso_ext_date)
34 {
35 	if (iso_ext_date.length == 0) return "---";
36 	return formatDateTime(SysTime.fromISOExtString(iso_ext_date));
37 }
38 
39 string formatDateTime(SysTime st)
40 {
41 	return st.toSimpleString();
42 }
43 
44 string formatFuzzyDate(Json dateTime)
45 {
46 	return formatFuzzyDate(dateTime.opt!string);
47 }
48 
49 string formatFuzzyDate(string iso_ext_date)
50 {
51 	if (iso_ext_date.length == 0) return "---";
52 	return formatFuzzyDate(SysTime.fromISOExtString(iso_ext_date));
53 }
54 
55 string formatFuzzyDate(SysTime st)
56 {
57 	auto now = Clock.currTime(UTC());
58 
59 	// TODO: proper singular forms etc. (probably done together with l8n)
60 	auto tm = now - st;
61 	if (tm < dur!"seconds"(0)) return "still going to happen";
62 	else if (tm < dur!"seconds"(1)) return "just now";
63 	else if (tm < dur!"minutes"(1)) return "less than a minute ago";
64 	else if (tm < dur!"minutes"(2)) return "a minute ago";
65 	else if (tm < dur!"hours"(1)) return format("%s minutes ago", tm.total!"minutes"());
66 	else if (tm < dur!"hours"(2)) return "an hour ago";
67 	else if (tm < dur!"days"(1)) return format("%s hours ago", tm.total!"hours"());
68 	else if (tm < dur!"days"(2)) return "a day ago";
69 	else if (tm < dur!"weeks"(5)) return format("%s days ago", tm.total!"days"());
70 	else if (tm < dur!"weeks"(52)) {
71 		auto m1 = st.month;
72 		auto m2 = now.month;
73 		auto months = (now.year - st.year) * 12 + m2 - m1;
74 		if (months == 1) return "a month ago";
75 		else return format("%s months ago", months);
76 	} else if (now.year - st.year <= 1) return "a year ago";
77 	else return format("%s years ago", now.year - st.year);
78 }
79 
80 string formatScore(string rootDir, float score)
81 {
82 	return format(`<a href="%sdevelop#package-scoring">%.1f</a>`, rootDir, score);
83 }
84 
85 string formatPackageStats(Stats)(Stats s)
86 {
87 	return format("%.1f\n\n#downloads / m: %s\n#stars: %s\n#watchers: %s\n#forks: %s\n#issues: %s\n",
88 				  s.score, s.downloads.monthly, s.repo.stars, s.repo.watchers, s.repo.forks, s.repo.issues);
89 }
90 
91 /** Takes an input range of version strings and returns the index of the "best"
92 	version.
93 
94 	"Best" version in this case means the highest released version. Numbered
95 	versions will be preferred over branch names.
96 
97 */
98 size_t getBestVersionIndex(R)(R versions)
99 {
100 	import dub.semver;
101 
102 	size_t ret = size_t.max;
103 	string retv;
104 	size_t i = 0;
105 	foreach (vstr; versions) {
106 		if (ret == size_t.max) {
107 			ret = i;
108 			retv = vstr;
109 		} else {
110 			if (retv.startsWith("~")) {
111 				if (vstr == "~master" || !vstr.startsWith("~")) {
112 					ret = i;
113 					retv = vstr;
114 				}
115 			} else if (!vstr.startsWith("~") && compareVersions(vstr, retv) > 0) {
116 				ret = i;
117 				retv = vstr;
118 			}
119 		}
120 		i++;
121 	}
122 	return ret;
123 }
124 
125 unittest {
126 	assert(getBestVersionIndex(["~master", "0.0.1", "1.0.0"]) == 2);
127 	assert(getBestVersionIndex(["~master", "0.0.1-alpha"]) == 1);
128 	assert(getBestVersionIndex(["~somebranch", "~master"]) == 1);
129 	assert(getBestVersionIndex(["~master", "~somebranch"]) == 0);
130 	assert(getBestVersionIndex(["1.0.0", "1.0.1-alpha"]) == 1);
131 }