Skip to content

Commit 1e94b8e

Browse files
committed
feat(mcl): Implement dev_commit command
1 parent 34283e8 commit 1e94b8e

File tree

11 files changed

+319
-17
lines changed

11 files changed

+319
-17
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ jobs:
3737
- uses: actions/checkout@v4
3838

3939
- name: Build and test the `mcl` command
40-
run: nix develop -c sh -c "dub test --root packages/mcl -- -e 'fetchJson|(coda\.)|nix.run|nix.build'"
40+
run: nix develop -c sh -c "dub test --root packages/mcl --compiler ldc2 -- -e 'fetchJson|(coda\.)|nix.run|nix.build'"
4141

4242
ci:
4343
uses: ./.github/workflows/reusable-flake-checks-ci-matrix.yml

flake.lock

Lines changed: 4 additions & 4 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/feat/build-dub-package";
201201
inputs = {
202202
flake-compat.follows = "flake-compat";
203203
flake-parts.follows = "flake-parts";

packages/default.nix

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969
// optionalAttrs (system == "x86_64-linux") {
7070
mcl = pkgs.callPackage ./mcl {
7171
buildDubPackage = inputs'.dlang-nix.legacyPackages.buildDubPackage.override {
72-
ldc = inputs'.dlang-nix.packages."ldc-binary-1_34_0";
72+
dCompiler = inputs'.dlang-nix.packages."ldc-binary-1_38_0";
7373
};
7474
inherit (legacyPackages.inputs.nixpkgs) cachix nix nix-eval-jobs;
7575
};

packages/mcl/default.nix

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ let
2626
]);
2727
excludedTests = (
2828
lib.concatStringsSep "|" [
29-
"(nix\\.(build|run))"
29+
"(nix\\.(build|run|eval))"
3030
"fetchJson|(coda\.)"
3131
"checkPackage"
3232
"generateShardMatrix"
@@ -52,17 +52,15 @@ buildDubPackage rec {
5252
nativeBuildInputs = [ pkgs.makeWrapper ] ++ deps;
5353

5454
postFixup = ''
55-
wrapProgram $out/bin/${pname} --set PATH "${lib.makeBinPath deps}"
55+
wrapProgram $out/bin/${pname} --set PATH "${lib.makeBinPath deps}" --set LD_LIBRARY_PATH "${lib.makeLibraryPath deps}"
5656
'';
5757

5858
dubBuildFlags = [
59-
"--compiler=dmd"
6059
"-b"
6160
"debug"
6261
];
6362

6463
dubTestFlags = [
65-
"--compiler=dmd"
6664
"--"
6765
"-e"
6866
excludedTests

packages/mcl/dub.sdl

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ buildType "unittest-debug" {
1515
}
1616

1717
dflags "-preview=in"
18-
dflags "-preview=shortenedMethods"
19-
dflags "-defaultlib=libphobos2.so" platform="dmd"
2018
lflags "-fuse-ld=gold" platform="dmd"
2119
dflags "-mcpu=generic" platform="ldc"
2220
dflags "-mcpu=baseline" platform="dmd"

packages/mcl/src/main.d

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ alias supportedCommands = imported!`std.traits`.AliasSeq!(
1616
cmds.shard_matrix,
1717
cmds.host_info,
1818
cmds.ci,
19-
cmds.machine_create
19+
cmds.machine_create,
20+
cmds.dev_commit
2021
);
2122

2223
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)