forked from dlang/tools
-
Notifications
You must be signed in to change notification settings - Fork 0
/
changed.d
205 lines (165 loc) · 5.98 KB
/
changed.d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
// Written in the D programming language
/**
Change log generator which fetches the list of bugfixes
from the D Bugzilla between the given dates.
It stores its result in DDoc form to a text file.
Copyright: Dmitry Olshansky 2013.
License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
Authors: Dmitry Olshansky,
Andrej Mitrovic
Example usage:
---
rdmd changed.d --start=2013-01-01 --end=2013-04-01
rdmd changed.d --start=2013-01-01 (end date implicitly set to current date)
---
$(B Note:) The script will cache the results of an invocation, to avoid
re-querying bugzilla when invoked with the same arguments.
Use the --nocache option to override this behavior.
*/
// NOTE: this script requires libcurl to be linked in (usually done by default).
module changed;
import std.net.curl, std.conv, std.exception, std.algorithm, std.csv, std.typecons,
std.stdio, std.datetime, std.array, std.string, std.file, std.format, std.getopt,
std.path;
string[dchar] charToValid;
shared static this()
{
charToValid = [':' : "_", ' ': ""];
}
/** Return a valid file name. */
string normalize(string input)
{
return input.translate(charToValid);
}
/** Generate location for the cache file. */
string getCachePath(string start_date, string end_date)
{
return buildPath(tempDir(),
format("dlog_%s_%s_%s_%s",
__DATE__, __TIME__, start_date, end_date).normalize());
}
auto templateRequest =
`https://issues.dlang.org/buglist.cgi?username=crap2crap%40yandex.ru&password=powerlow7&chfieldto={to}&query_format=advanced&chfield=resolution&chfieldfrom={from}&bug_status=RESOLVED&resolution=FIXED&product=D&ctype=csv&columnlist=component%2Cbug_severity%2Cshort_desc`;
auto generateRequest(string templ, Date start, Date end)
{
auto ss = format("%04s-%02s-%02s", start.year, to!int(start.month), start.day);
auto es = format("%04s-%02s-%02s", end.year, to!int(end.month), end.day);
return templateRequest.replace("{from}", ss).replace("{to}", es);
}
auto dateFromStr(string sdate)
{
int year, month, day;
formattedRead(sdate, "%s-%s-%s", &year, &month, &day);
return Date(year, month, day);
}
struct Entry
{
int id;
string summary;
}
string[dchar] parenToMacro;
shared static this()
{
parenToMacro = ['(' : "$(LPAREN)", ')' : "$(RPAREN)"];
}
/** Replace '(' and ')' with macros to avoid closing down macros by accident. */
string escapeParens(string input)
{
return input.translate(parenToMacro);
}
/** Generate and return the change log as a string. */
string getChangeLog(Date start, Date end)
{
auto req = generateRequest(templateRequest, start, end);
debug stderr.writeln(req); // write text
auto data = req.get;
// component (e.g. DMD) -> bug type (e.g. regression) -> list of bug entries
Entry[][string][string] entries;
immutable bugtypes = ["regressions", "bugs", "enhancements"];
immutable components = ["DMD Compiler", "Phobos", "Druntime", "Optlink", "Installer", "Website"];
foreach (fields; csvReader!(Tuple!(int, string, string, string))(data, null))
{
string comp = fields[1].toLower;
switch (comp)
{
case "dmd": comp = "DMD Compiler"; break;
case "websites": comp = "Website"; break;
default: comp = comp.capitalize;
}
string type = fields[2].toLower;
switch (type)
{
case "regression":
type = "regressions";
break;
case "blocker", "critical", "major", "normal", "minor", "trivial":
type = "bugs";
break;
case "enhancement":
type = "enhancements";
break;
default: assert(0, type);
}
entries[comp][type] ~= Entry(fields[0], fields[3].idup);
}
Appender!string result;
result ~= "$(BUGSTITLE Language Changes,\n";
result ~= "-- Insert major language changes here --\n)\n\n";
result ~= "$(BUGSTITLE Library Changes,\n";
result ~= "-- Insert major library changes here --\n)\n\n";
result ~= "$(BR)$(BIG List of all bug fixes and enhancements:)\n\n";
foreach (component; components)
if (auto comp = component in entries)
{
foreach (bugtype; bugtypes)
if (auto bugs = bugtype in *comp)
{
result ~= format("$(BUGSTITLE %s %s,\n\n", component, bugtype);
result ~= "$(P\n";
foreach (bug; sort!"a.id < b.id"(*bugs))
{
result ~= format("$(LI $(BUGZILLA %s): %s)\n",
bug.id, bug.summary.escapeParens());
}
result ~= ")\n";
result ~= ")\n";
}
}
return result.data;
}
int main(string[] args)
{
string start_date, end_date;
bool ddoc = false;
bool nocache; // don't read from cache
getopt(args,
"nocache", &nocache,
"start", &start_date, // numeric
"end", &end_date); // string
if (start_date.empty)
{
stderr.writefln("*ERROR: No start date set.\nUsage example:\n%s --start=YYYY-MM-HH [--end=YYYY-MM-HH] ",
args[0].baseName);
return 1;
}
Date start = dateFromStr(start_date);
Date end = end_date.empty ? to!Date(Clock.currTime()) : dateFromStr(end_date);
// caching to avoid querying bugzilla
// (depends on the compile date of the generator + the start and end dates)
string cachePath = getCachePath(to!string(start), to!string(end));
debug stderr.writefln("Cache file: %s\nCache file found: %s", cachePath, cachePath.exists);
string changeLog;
if (!nocache && cachePath.exists)
{
changeLog = (cast(string)read(cachePath)).strip;
}
else
{
changeLog = getChangeLog(start, end);
std.file.write(cachePath, changeLog);
}
string logPath = "./changelog.txt".absolutePath.buildNormalizedPath;
std.file.write(logPath, changeLog);
writefln("Change log generated to: '%s'", logPath);
return 0;
}