-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.ts
141 lines (126 loc) · 3.71 KB
/
main.ts
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
import { countCharacter, Result as Count } from "@suin/mdast-character-count";
import fs from "fs";
import mdastUtilFromMarkdown from "mdast-util-from-markdown";
import "zx/globals";
const syntax = require("micromark-extension-gfm");
const gfm = require("mdast-util-gfm");
const url = "https://github.com/yytypescript/book.git";
const dir = "repo";
const stats: Stats = new Map();
await cloneRepository(url);
cd(dir);
for await (const commitId of getCommitIds()) {
console.time("commit");
await checkout(commitId);
const date = getDate(await getAuthorDate());
const files = await getMarkdownFiles();
const stat: Stat = { files: files.length, text: 0, code: 0 };
const results: Array<Promise<Count>> = [];
for (const file of await getMarkdownFiles()) {
results.push(getCount(`${dir}/${file}`));
}
for (const { textCharacters, codeCharacters } of await Promise.all(results)) {
stat.text += textCharacters;
stat.code += codeCharacters;
}
fillNoDataDateWithStats(stats, date);
stats.set(date, stat);
console.log(stats);
console.timeEnd("commit");
}
saveStatsCsv(stats);
saveStatsJson(stats);
async function cloneRepository(url: string): Promise<void> {
if (fs.existsSync(dir)) {
return;
}
await $`git clone ${url} ${dir}`;
}
async function* getCommitIds(): AsyncGenerator<string> {
const { stdout } = await $`git log --reverse --pretty=%H`;
for (const commitId of stdout.split("\n")) {
if (commitId === "") {
continue;
}
yield commitId;
}
}
async function checkout(commitId: string): Promise<void> {
await $`git checkout ${commitId}`;
}
async function getAuthorDate(): Promise<Date> {
const { stdout } = await $`git show --pretty="%cI" --no-patch`;
return new Date(stdout.trim());
}
function getDate(date: Date): string {
const format = new Intl.DateTimeFormat("en-CA", {
year: "numeric",
month: "numeric",
day: "numeric",
timeZone: "Asia/Tokyo",
});
return format.format(date);
}
async function getMarkdownFiles(): Promise<ReadonlyArray<string>> {
const { stdout } =
await $`find . -type f -name "*.md" -not -path './writing/*' -not -path './docs/writing/*' -not -path './prh/*' -not -path './next/*'`;
return stdout.trim().split("\n");
}
function getCount(file: string): Promise<Count> {
return new Promise((resolve, reject) => {
const content = fs.readFileSync(file, "utf-8");
try {
const tree = mdastUtilFromMarkdown(content, {
extensions: [syntax()],
mdastExtensions: [gfm.fromMarkdown],
});
resolve(countCharacter(tree));
} catch (e) {
reject(e);
}
});
}
type Stat = {
files: number;
text: number;
code: number;
};
type Stats = Map<string, Stat>;
function saveStatsCsv(stats: Stats) {
let content = "date,files,text,code\n";
for (const [date, { files, text, code }] of stats) {
content +=
[
date,
files.toString().padStart(3, " "),
text.toString().padStart(6, " "),
code.toString().padStart(6, " "),
].join(",") + "\n";
}
fs.writeFileSync("stats.csv", content);
}
function saveStatsJson(stats: Stats) {
let content: Array<Stat & { date: string }> = [];
for (const [date, { files, text, code }] of stats) {
content.push({ date, files, text, code });
}
fs.writeFileSync("stats.json", JSON.stringify(content, null, 2));
}
function fillNoDataDateWithStats(stats: Stats, nextDate: string) {
if (stats.size === 0) {
return;
}
const [last, lastStat] = [...stats].slice(-1)[0];
if (last === nextDate) {
return;
}
const accDate = new Date(last);
while (true) {
accDate.setDate(accDate.getDate() + 1);
let date = getDate(accDate);
if (date === nextDate) {
break;
}
stats.set(date, lastStat);
}
}