Skip to content

Commit 58072e6

Browse files
committed
feat(mcl): Implement dev_commit command
1 parent ac64303 commit 58072e6

File tree

8 files changed

+318
-14
lines changed

8 files changed

+318
-14
lines changed

flake.lock

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

flake.nix

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@
197197
};
198198

199199
dlang-nix = {
200-
url = "github:PetarKirov/dlang.nix?branch=feat/build-dub-package&rev=dab4c199ad644dc23b0b9481e2e5a063e9492b84";
200+
url = "github:PetarKirov/dlang.nix?branch=feat/build-dub-package&rev=21b1b3b18b3b635a43b319612aff529d26b1863b";
201201
inputs = {
202202
flake-compat.follows = "flake-compat";
203203
flake-parts.follows = "flake-parts";

packages/mcl/default.nix

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
deps = with pkgs; [
99
cachix
1010
git
11+
gpg
1112
nix
1213
nom
1314
nix-eval-jobs
@@ -28,7 +29,7 @@
2829
];
2930
excludedTests = (
3031
lib.concatStringsSep "|" [
31-
"(nix\\.(build|run))"
32+
"(nix\\.(build|run|eval))"
3233
"fetchJson|(coda\.)"
3334
"checkPackage"
3435
]

packages/mcl/dub.sdl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ buildType "unittest-debug" {
1414
buildOptions "unittests" "debugMode" "debugInfo"
1515
}
1616

17-
dflags "-preview=shortenedMethods"
1817
dflags "-defaultlib=libphobos2.so" platform="dmd"
1918
lflags "-fuse-ld=gold" platform="dmd"
2019
dflags "-mcpu=generic" platform="ldc"

packages/mcl/src/main.d

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ alias supportedCommands = imported!`std.traits`.AliasSeq!(
1313
cmds.shard_matrix,
1414
cmds.host_info,
1515
cmds.ci,
16-
cmds.machine_create
16+
cmds.machine_create,
17+
cmds.dev_commit
1718
);
1819

1920
int main(string[] args)
Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
module mcl.commands.dev_commit;
2+
3+
import std.algorithm : any, cache, canFind, filter, find, map, sort, startsWith, uniq;
4+
import std.array : appender, array, assocArray, front, join, replace, split;
5+
import std.conv : to;
6+
import std.file : dirEntries, exists, readText, SpanMode;
7+
import std.json : JSONOptions, parseJSON;
8+
import std.parallelism : parallel, taskPool;
9+
import std.path : globMatch, stripExtension;
10+
import std.process : ProcessPipes, wait;
11+
import std.regex : ctRegex, match, Regex, regex, replaceAll, replaceFirst;
12+
import std.stdio : writeln;
13+
import std.string : indexOf, startsWith, strip;
14+
import std.typecons : tuple;
15+
import mcl.utils.env : parseEnv, optional;
16+
import mcl.utils.process : execute;
17+
import mcl.utils.path : rootDir;
18+
import mcl.utils.log : prompt;
19+
import mcl.utils.json : fromJSON;
20+
21+
string[] modifiedFiles = [];
22+
static const enum CommitType
23+
{
24+
feat,
25+
fix,
26+
refactor,
27+
ci,
28+
docs,
29+
style,
30+
config,
31+
build,
32+
chore,
33+
perf,
34+
test
35+
}
36+
37+
struct Config
38+
{
39+
struct Exclude
40+
{
41+
string[] startsWith = [];
42+
string[] contains = [".gitkeep"];
43+
string[] equals = [
44+
"src", "packages", "pkg", "pkgs", "apps", "libs", "modules",
45+
"services", ".git"
46+
];
47+
}
48+
49+
struct Scope
50+
{
51+
string[string] replaceAll = [
52+
"(src|packages|pkg|pkgs|apps|libs|modules|services)/": "",
53+
"mcl/mcl/": "mcl/",
54+
"mcl/commands/": "mcl/",
55+
"(docs.*/)?(pages/)?docs/": "docs/"
56+
];
57+
string[string] replaceFirst = [
58+
"^docs/": "",
59+
"^nix/": "",
60+
"/(default|main|index|start|app|init|__init__|entry|package)$": ""
61+
];
62+
}
63+
64+
struct Type
65+
{
66+
CommitType[string] equals = [
67+
".gitignore": CommitType.config,
68+
];
69+
CommitType[string] contains;
70+
CommitType[string] startsWith = [
71+
"docs": CommitType.docs,
72+
".github/": CommitType.ci,
73+
".gitlab/": CommitType.ci,
74+
75+
];
76+
}
77+
78+
Exclude exclude;
79+
Scope _scope;
80+
Type type;
81+
}
82+
83+
static Config config;
84+
85+
void initGitDiff()
86+
{
87+
auto status = execute("git diff --name-only --cached", false).split("\n")
88+
.map!(a => a.strip)
89+
.cache
90+
.filter!((a) {
91+
if (config.exclude.equals.canFind(a))
92+
return false;
93+
else if (config.exclude.contains.any!(c => a.indexOf(c) != -1))
94+
return false;
95+
else if (config.exclude.startsWith.any!(c => a.startsWith(c)))
96+
return false;
97+
else
98+
return true;
99+
})
100+
.array;
101+
if (status.length)
102+
{
103+
modifiedFiles = status
104+
.map!(a => stripExtension(a.strip)).array;
105+
writeln("Modified files (staged): ");
106+
writeln(status.map!(f => "> " ~ f).array.join("\n"));
107+
writeln("\n");
108+
}
109+
}
110+
111+
CommitType guessType()
112+
{
113+
if (modifiedFiles.length)
114+
{
115+
foreach (string file; modifiedFiles)
116+
{
117+
auto contains = config.type.contains.keys.find!(k => file.indexOf(k) != -1);
118+
auto startsWith = config.type.startsWith.keys.find!(k => file.startsWith(k));
119+
120+
if (config.type.equals.keys.canFind(file))
121+
{
122+
return config.type.equals[file];
123+
}
124+
else if (contains.length)
125+
{
126+
return config.type.contains[contains.front];
127+
}
128+
else if (startsWith.length)
129+
{
130+
return config.type.startsWith[startsWith.front];
131+
}
132+
}
133+
}
134+
return CommitType.feat;
135+
}
136+
137+
string[] guessScope()
138+
{
139+
140+
Regex!char[string] replaceAllRegexes = config._scope.replaceAll.keys.map!(
141+
key => tuple(key, regex(key, "g"))).assocArray;
142+
Regex!char[string] replaceFirstRegexes = config._scope.replaceFirst.keys.map!(
143+
key => tuple(key, regex(key, "g"))).assocArray;
144+
145+
auto files = modifiedFiles
146+
.map!((a) {
147+
foreach (i, value; config._scope.replaceAll)
148+
{
149+
a = a.replaceAll(replaceAllRegexes[i], value);
150+
}
151+
foreach (i, value; config._scope.replaceFirst)
152+
{
153+
a = a.replaceFirst(replaceFirstRegexes[i], value);
154+
}
155+
return a;
156+
}
157+
)
158+
.array
159+
.sort
160+
.uniq
161+
.array;
162+
return files;
163+
}
164+
165+
static immutable auto botRegex = ctRegex!(`(\[bot\]|dependabot|actions-bot)`);
166+
167+
string[] getAuthors()
168+
{
169+
auto authors = execute("git log --format='%aN' | sort -u", false).split("\n");
170+
return authors
171+
.filter!(a => !match(a, botRegex))
172+
.map!(a => a.strip)
173+
.array ~ [""];
174+
}
175+
176+
struct CommitParams
177+
{
178+
CommitType type;
179+
string _scope;
180+
string shortDescription;
181+
string description;
182+
bool isBreaking;
183+
string breaking;
184+
bool isIssue;
185+
int issue;
186+
string[] coAuthors;
187+
}
188+
189+
string createCommitMessage(CommitParams params)
190+
{
191+
auto strBuilder = appender!string;
192+
strBuilder.put(params.type.to!string);
193+
strBuilder.put("(");
194+
strBuilder.put(params._scope);
195+
strBuilder.put("): ");
196+
strBuilder.put(params.shortDescription);
197+
if (params.description.length)
198+
{
199+
strBuilder.put("\n\n");
200+
strBuilder.put(params.description);
201+
}
202+
if (params.isBreaking)
203+
{
204+
strBuilder.put("\n\nBREAKING CHANGE:");
205+
strBuilder.put(params.breaking);
206+
207+
}
208+
if (params.isIssue)
209+
{
210+
strBuilder.put("\n\nCloses #");
211+
strBuilder.put(params.issue.to!string);
212+
}
213+
if (params.coAuthors.length)
214+
{
215+
strBuilder.put("\n\nCo-authored-by: ");
216+
strBuilder.put(params.coAuthors.join(", "));
217+
}
218+
return strBuilder.toString();
219+
}
220+
221+
CommitParams promptCommitParams(bool automatic)
222+
{
223+
CommitParams commitParams;
224+
commitParams.type = automatic ? guessType
225+
: prompt!CommitType("Commit type (suggestion: " ~ guessType.to!string ~ ")");
226+
auto scopeSuggestion = guessScope;
227+
commitParams._scope = automatic ? scopeSuggestion.front
228+
: prompt!string(
229+
"Scope (suggestion: " ~ scopeSuggestion.join(", ") ~ ")");
230+
commitParams.shortDescription = automatic ? "" : prompt!string("Short Description");
231+
commitParams.description = automatic ? "" : prompt!string("Description");
232+
commitParams.isBreaking = automatic ? false : prompt!bool("Breaking change");
233+
commitParams.breaking = commitParams.isBreaking ? prompt!string("Breaking change description")
234+
: "";
235+
commitParams.isIssue = automatic ? false : prompt!bool(
236+
"Does this commit relate to an existing issue");
237+
if (commitParams.isIssue)
238+
{
239+
commitParams.issue = prompt!int("Issue number");
240+
}
241+
commitParams.coAuthors = automatic ? [] : prompt!string(
242+
"Co-authors (comma separated)", getAuthors).split(",").map!(a => a.strip)
243+
.cache
244+
.filter!(a => a != "")
245+
.array;
246+
return commitParams;
247+
}
248+
249+
export void dev_commit()
250+
{
251+
Params params = parseEnv!Params;
252+
253+
string mclFile = rootDir ~ "/.mcl.json";
254+
if (mclFile.exists)
255+
config = parseJSON(readText(mclFile), JSONOptions.none).fromJSON!Config;
256+
257+
initGitDiff();
258+
259+
CommitParams commitParams = promptCommitParams(params.automatic);
260+
261+
writeln();
262+
string commitMessage = createCommitMessage(commitParams);
263+
writeln(commitMessage);
264+
writeln();
265+
266+
bool commit = prompt!bool("Commit?");
267+
if (commit)
268+
{
269+
auto pipes = execute!ProcessPipes("git commit -F -", false);
270+
pipes.stdin.writeln(commitMessage);
271+
pipes.stdin.flush();
272+
pipes.stdin.close();
273+
writeln(pipes.stdout.byLineCopy.array.join("\n"));
274+
writeln(pipes.stderr.byLineCopy.array.join("\n"));
275+
wait(pipes.pid);
276+
}
277+
}
278+
279+
struct Params
280+
{
281+
@optional() bool automatic = false;
282+
void setup()
283+
{
284+
}
285+
}

packages/mcl/src/src/mcl/commands/package.d

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ public import mcl.commands.shard_matrix : shard_matrix;
77
public import mcl.commands.ci : ci;
88
public import mcl.commands.host_info : host_info;
99
public import mcl.commands.machine_create : machine_create;
10+
public import mcl.commands.dev_commit : dev_commit;

0 commit comments

Comments
 (0)