├── .gitignore
├── README.md
├── config
├── config.js
└── default.template.md
├── doc.md
├── example
├── gitwork.out.month-week.html
├── gitwork.out.month-week.md
├── gitwork.out.week.html
└── gitwork.out.week.md
├── index.js
├── lib
└── opener.js
├── package.json
├── util.js
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .history
3 | *.out.md
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # gitwork
2 | 一秒写出漂亮的工作报告!
3 |
4 | **效果演示**
5 |
6 | - [👉 多项目 week 格式的报告](https://wll8.github.io/gitday/example/gitwork.out.week.html)
7 | - [👉 多项目 month-week 格式的报告](https://wll8.github.io/gitday/example/gitwork.out.month-week.html)
8 |
9 | ## 为什么开发它
10 | 程序员的工作基本都在编码, 所以我们可以直接读取避免记录生成工作报告. 对于每周都在开几次任务进度会, 还得每周都要写工作报告, 在工作报告中, 有注意到大家的工作报告基本都是千篇一律:
11 |
12 | 测试:
13 | > 本周创建了 x 个 bug, 测试了 x 个bug, 关闭了 x 个 bug... 下周计划: 编写测试报告, 继续测试.
14 |
15 | 开发:
16 | > 本周修复 xx bug, 开发了 xx 功能. 下周计划: 修复 xx bug, 开发 xx 功能.
17 |
18 | 周某:
19 | > 意义在哪里? 这得人工写? 我周某人就是不惯着!
20 |
21 | ## 功能
22 | - 支持任意时间段的报告
23 | - 高度可自定义, 例如模板/变量/git作者名/项目名转换...
24 | - 支持根据 commit message 规范转换任务风格, 例如 `fix(cli): xxx` 将被转换为 `🐛修复cli中的缺陷: xxx`
25 | - 支持模板插槽
26 | - 支持从多个项目目录中读取数据生成一个工作报告
27 | - 支持自动过滤较高相似并的任务(避免看起来一个任务在报告中出现了几次)
28 | - [ ] 支持生成工作小结
29 | - [ ] 支持直接转换为 pdf/邮件/html/jpeg/...
30 |
31 | ## 使用
32 | ``` sh
33 | # 安装
34 | npm i -g gitwork
35 |
36 | # 生成当月工作报告
37 | gitwork
38 |
39 | # 指定开始时间生成报告
40 | gitwork after=2021-01-01
41 |
42 | # 生成周报
43 | gitwork template=week
44 |
45 | # 查看使用说明
46 | gitwork --help
47 |
48 | # 查看版本号
49 | gitwork -v
50 |
51 | # 打开配置文件所在位置
52 | gitwork --config
53 | ```
54 |
55 | ## 命令行参数
56 | - -v, --version 显示程序版本号
57 | - --help 显示帮助页面
58 | - --config 打开配置文件所在位置
59 | - --select 选择报告配置
60 |
61 | ## 可配置项
62 | 这些选项来自配置文件,你可以使用 `gitwork --config` 打开配置文件所在位置,也可以在使用时通过命令行设置报告参数, 例如 `gitwork author=wll8` 。
63 |
64 | - select
65 | - [x] 你可以使用配置文件保存多个报告,批量生成它们,多个使用逗号分割。
66 | - 可选值
67 | - default 程序默认的报告配置
68 | - 其他自己在配置文件中添加的模板标志
69 | - 默认值 `default`
70 | - layout
71 | - [ ] 布局方式
72 | - 可选值
73 | - repository-time 先仓库后时间
74 | - time-repository 先时间后仓库
75 | - 默认值 `repository-time`
76 | - author
77 | - [x] 指定要生成报告的 git 用户名
78 | - 可选值
79 | - 其他你自己指定的用户名,多个使用逗号分割
80 | - 默认值 `当前 git 用户`, 即 `git config user.name` 的值
81 | - ignoreAuthor
82 | - 是否忽略按用户名进行过滤
83 | - 可选值
84 | - true
85 | - false
86 | - 默认值 false
87 | - authorName
88 | - [x] 实际输出到报告中的名称, 例如 git 用户名和报告中所需姓名不同时
89 | - 默认值 `当前 git 用户`
90 | - template
91 | - [x] 报告的时间编排方式
92 | - 可选值
93 | - month
94 | - month-week
95 | - month-week-day
96 | - week
97 | - week-day
98 | - day
99 | - 默认值 `month-week`
100 | - messageBody
101 | - 如何处理 git commit message 的 body 部分, 由于它的内容不可控, 可能会破坏报告
102 | - 可选值
103 | - raw 原样使用
104 | - none 不使用
105 | - compatible 当含有可能破坏报告的内容时不使用
106 | - 默认值: `compatible`
107 | - useFile
108 | - [x] 使用文件模板, 相对于配置文件目录
109 | - 默认值: `./default.template.md`
110 | - after
111 | - 开始时间, YYYY-MM-DD 格式
112 | - 默认值 `根据 template 转换`
113 | - before
114 | - 结束时间, YYYY-MM-DD 格式
115 | - 默认值 `根据 template 转换`
116 | - outFile
117 | - [ ] 输出文件, 支持 .md .html .pdf .jpeg .word .xlsx, 相对于运行目录
118 | - 默认值: `./gitwork.out.md`
119 | - insertFile
120 | - [ ] 插入文件, 相对于运行目录
121 | - 默认值: `./todo.md`
122 | - rootLevel
123 | - [x] 从多少个#号开始表示生成内容中最高级别标题, 不包含文档根结点标题, 根节点标题的级别应在 report.title 中添加
124 | - 默认值: 2
125 | - repository
126 | - [x] 从哪些仓库中生成报告
127 | - 可选值
128 | - string 本地仓库绝对路径, 多个时使用逗号分割
129 | - array
130 | - path 本地仓库绝对路径
131 | - name 项目名称
132 | - 默认值: 当前仓库
133 | - noEqualMsg
134 | - [x] 过滤掉重复的 msg
135 | - 可选值
136 | - true
137 | - false
138 | - 默认值: true
139 | - similarity
140 | - [x] 过滤掉大于给定相似值的 msg
141 | - 可选值
142 | - 0 到 1 之前的值
143 | - 默认值: 0.75
144 | - messageTypeSimilarity
145 | - [x] 配置 message type 的相似程度
146 | - 可选值
147 | - 0 到 1 之前的值
148 | - 默认值: 0.8
149 | - noUnknownType
150 | - [x] 是否移除未知的 type
151 | - 可选值
152 | - true
153 | - false
154 | - 默认值: false
155 | - messageTypeTemplate
156 | - [x] 配置 message 的生成模板
157 | - 可选值
158 | - 可使用字符串模板, 支持使用 messageConvert[type].des 中的变量
159 | - 默认值: `#{emoji}#{text}`
160 | - messageConvert
161 | - [x] 配置 message 的详细生成规则
162 | - 可选值
163 | - false 不进行转换
164 | - object [自定义请参考](./config/config.js)
165 | - [type] git commit message type 标志
166 | - alias array type 别名
167 | - des type 转换配置
168 | - emoji 表情
169 | - text type 和 scope 的转换模板
170 | - 0 没有 scope 时的模板
171 | - 1 有 scope 时的模板
172 | - 默认值: [参考 config.js](./config/config.js)
173 |
174 | ## todo
175 | - [ ] fix: 应使用保险的方式检查 messageBody 中的内容
176 | - [ ] feat: 如果只有一个时间结点时, 则不显示它. 比如下面内容, 当为本周周报时, 重复显示 `2022年07月 第4周` 是没有意义的.
177 | - [ ] feat: 格式化输出
178 | - [x] 每个 commit 前插入一个空行
179 | - [ ] 多行 commit 消息时再每行后面添加两个空格, 这样默认 markdown 才会显示换行效果
180 | - [x] 移除 commit title 与 body 之前的空行
181 | - [x] 移除 body 后面多于的空行
182 | - [ ] 合并两个空行为一个
183 | - [x] 转换 msg 提交标志, 例如转换 `fix(client): xxx` 为 `修复 client 中的缺陷: xxx`
--------------------------------------------------------------------------------
/config/config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | report: [
3 | {
4 | select: `default`, // 默认配置, 不可删除
5 | layout: `repository-time`, //
6 | author: [], // 当前用户名
7 | authorName: undefined, // 当前用户名
8 | template: `month-week`,
9 | messageBody: `compatible`,
10 | useFile: `./default.template.md`,
11 | after: ``,
12 | before: ``,
13 | outFile: `./${GET(`package`).name}.out.md`,
14 | insertFile: undefined,
15 | noEqualMsg: true,
16 | rootLevel: 2,
17 | similarity: 0.75,
18 | repository: [], // 当前项目
19 | messageTypeSimilarity: 0.8,
20 | messageTypeTemplate: `@{emoji}@{text}`,
21 | useMessageConvert: true,
22 | messageConvert: {
23 | feat: {
24 | alias: [`feature`],
25 | des: {
26 | emoji: `✨`,
27 | text: [`添加功能: `, `在@{scope}中添加新功能: `],
28 | },
29 | },
30 | fix: {
31 | alias: [`fixed`],
32 | des: {
33 | emoji: `🐛`,
34 | text: [`修复缺陷: `, `修复@{scope}中的缺陷: `],
35 | },
36 | },
37 | doc: {
38 | alias: [`docs`],
39 | des: {
40 | emoji: `📝`,
41 | text: [`更新文档: `, `更新@{scope}中的文档: `],
42 | },
43 | },
44 | style: {
45 | alias: [`styles`],
46 | des: {
47 | emoji: `🌈`,
48 | text: [`优化代码可读性: `, `优化@{scope}中的代码可读性: `],
49 | },
50 | },
51 | build: {
52 | alias: [],
53 | des: {
54 | emoji: `📦`,
55 | text: [`更新构建工具: `, `更新@{scope}中的构建工具: `],
56 | },
57 | },
58 | refactor: {
59 | alias: [],
60 | des: {
61 | emoji: `♻️`,
62 | text: [`重构代码: `, `重构@{scope}中的代码: `],
63 | },
64 | },
65 | revert: {
66 | alias: [],
67 | des: {
68 | emoji: `⏪`,
69 | text: [`回退代码: `, `回退@{scope}中的代码: `],
70 | },
71 | },
72 | test: {
73 | alias: [],
74 | des: {
75 | emoji: `🚨`,
76 | text: [`更新测试逻辑: `, `更新@{scope}中的测试逻辑: `],
77 | },
78 | },
79 | perf: {
80 | alias: [],
81 | des: {
82 | emoji: `🚀`,
83 | text: [`提升性能: `, `提升@{scope}中的性能: `],
84 | },
85 | },
86 | ci: {
87 | alias: [],
88 | des: {
89 | emoji: `💚`,
90 | text: [`更新持续集成服务: `, `更新@{scope}中的持续集成服务: `],
91 | },
92 | },
93 | chore: {
94 | alias: [],
95 | des: {
96 | emoji: `🔧`,
97 | text: [`更新辅助工具: `, `更新@{scope}中的辅助工具: `],
98 | },
99 | },
100 | },
101 | ignoreAuthor: false,
102 | },
103 | ],
104 | }
--------------------------------------------------------------------------------
/config/default.template.md:
--------------------------------------------------------------------------------
1 | # @{authorName}的工作报告(@{after} 至 @{before})
2 |
3 | 大家好,以下内容是我的工作报告和后续计划,不足之处敬请指正。
4 |
5 | ## 工作报告
6 | 小结:在这个阶段中,主要精力集中在[XXX]这个项目,以及[修复缺陷]。
7 | 处理[xxx]这项工作消耗了比预期较多的时间,主要原因是[在实现功能上需要处理比较多的逻辑]。使用[xxx这种方案]获取可以得到更好的收益。
8 |
9 |
10 | ### 项目一
11 |
12 | - fix: xxx
13 | des...
14 |
15 | - fix: xxx
16 |
17 |
18 |
19 | ### 项目二
20 |
21 | - fix: xxx
22 | des...
23 |
24 | - fix: xxx
25 |
26 |
27 |
28 |
29 | ## 后续计划
30 |
31 | - item...
32 | - item...
33 |
34 |
--------------------------------------------------------------------------------
/doc.md:
--------------------------------------------------------------------------------
1 | ## git log
2 | https://git-scm.com/docs/git-log
3 |
4 | - --author 作者
5 | - --all 显示所有分支
6 | - --after=1.months 显示某个时间之后, 如果 authorDate 在两周前, 但 commitDate 在一周前, 也会被查出.
7 | - --no-merges 不包括 merges
8 |
9 |
10 |
11 | - 作者日期:最初创作提交的时间。通常,当有人第一次运行git commit.
12 | - 提交日期:将提交应用于分支的时间。在许多情况下,它与作者日期相同。有时它会有所不同:如果提交被修改、重新设置或由作者以外的人作为补丁的一部分应用。在这些情况下,日期将是变基发生或应用补丁的日期。
13 |
14 |
15 | ## git commit
16 | https://github.com/dannyfritz/commit-message-emoji
17 | https://medium.com/walmartglobaltech/semantic-commit-messages-with-emojis-dba2541cea9a
18 | https://babakks.github.io/article/2020/07/03/emojis-in-git-commit-messages.html
19 | https://gist.github.com/parmentf/035de27d6ed1dce0b36a
20 | https://wilsonmar.github.io/git-messages/
--------------------------------------------------------------------------------
/example/gitwork.out.month-week.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | gitwork.out.month-week.md
5 |
6 |
7 |
228 |
229 |
304 |
305 |
356 |
357 |
358 |
359 |
360 |
368 | 王小花的工作报告(2022-08-01 至 2022-08-31)
369 |
411 |
412 | 大家好,以下内容是我的工作报告和后续计划,不足之处敬请指正。
413 | 1. 工作报告
414 | 小结:在这个阶段中,主要精力集中在 工作报告生成器
这个项目,以及 添加功能
。 处理 支持过滤掉相似度较高的 msg
这项工作消耗了比预期较多的时间,主要原因是 在实现功能上需要处理比较多的逻辑
。
415 | 1.1. 工作报告生成器
416 | 1.1.1. 2022年08月
417 | 1.1.1.1. 第3周
418 |
419 | -
420 |
🔧更新辅助工具: 屏蔽无关日志, 使用 readme 作为 help 输出
421 |
422 | -
423 |
✨添加功能: 更新默认模板
424 |
425 | -
426 |
🐛修复缺陷: git log 结果仅有一条时不应出现错误
427 | Error: Bad arguments: First argument should be a string, second should be an array of strings
428 |
429 |
430 |
431 | 1.1.1.2. 第1周
432 |
433 | -
434 |
✨添加功能: 比较 commit msg 时忽略大小写
435 |
436 | -
437 |
✨添加功能: 支持 ignoreAuthor 参数, 是否忽略按用户名进行过滤
438 |
439 | -
440 |
✨添加功能: 使用 --branches 参数而不是 --all 参数
441 |
442 | -
443 |
🐛修复缺陷: 修复 --debug 参数失效
444 |
445 | -
446 |
✨添加功能: 从命令行覆盖配置项不再需要 -- 前缀
447 |
448 | -
449 |
🐛修复缺陷: 处理 --help 报错
450 |
451 | -
452 |
🐛修复缺陷: 需要显示当天内容
453 |
454 | - 例如当天是 2022-08-01, 并且当天有 commit, 但是运行
after=2022-08-01 before=2022-08-31
没有任何输出. 运行 after=2022-07-31 before=2022-07-31
也没有输出.
455 | - 应变更为包含当天
456 |
457 |
458 | -
459 |
✨添加功能: 转换 msg 提交标志
460 | 例如转换 fix(client): xxx
为 修复 client 中的缺陷: xxx
461 |
462 | -
463 |
✨添加功能: 支持过滤掉相似度较高的 msg
464 |
465 |
466 | 1.2. markdown 转换工具
467 | 1.2.1. 2022年08月
468 | 1.2.1.1. 第3周
469 |
470 | - 📝更新文档: update readme
471 | - 🐛修复缺陷: 不应屏蔽 console.log, 因为源插件使用它输出错误
472 |
473 | 1.2.1.2. 第2周
474 |
497 | 1.2.1.3. 第1周
498 |
499 | -
500 |
✨添加功能: 实现对象拦截, 目前验证 vscode 功能正常
501 |
502 | -
503 |
✨添加功能: 使用自己实现的方法简单实现拦截
504 |
505 | -
506 |
✨添加功能: 在 node 环境创建 vscode proxy
507 | 但是在 vscode 环境会导致 proxy 失败.
508 | 如果对 vscode 对象进行代理, 会报以下错误:
509 | TypeError: 'get' on proxy: property 'getText' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected 'getText(p){return p?w._getTextInRange(p):w.getText()}' but got 'getText(p){return p?w._getTextInRange(p):w.getText()}')
510 |
511 |
512 | -
513 |
🔧更新辅助工具: 添加 vsce package 和 npm build 命令
514 |
515 |
516 | 1.3. 接口伴侣
517 | 1.3.1. 2022年08月
518 | 1.3.1.1. 第3周
519 |
520 | - 🐛修复缺陷: 避免 config.openApi 配置为空数组时出现读取错误
521 |
522 | 1.3.1.2. 第1周
523 |
524 | -
525 |
✨添加功能: 调整一些依赖为可选项
526 | 注: yarn install 使用 --ignore-optional 或 --production 时依然会请求可选项中的依赖表文件
527 |
528 | -
529 |
✨添加功能: 把动态依赖放置于 optionalDependencies
530 | 因为动态安装经常不太稳定, 如果放置在可选依赖中则可以在第一次安装时尝试安装它们, 就算安装失败了也没有关系
531 |
532 | -
533 |
🐛修复缺陷: hasPackage 不应只检测子级 node_modules
534 | 例如全局安装时, npm 会把 node_modules 进行扁平化, 例如 md-cli 依赖了 mockm, 安装 md-cli 时, 只会存在 md-cli/node_modules 而不会存在 md-cli/node_modules/mockm/node_modules .
535 |
536 |
537 | 2. 后续计划
538 | 2.1. 工作报告生成器
539 |
543 |
544 |
545 |
546 |
--------------------------------------------------------------------------------
/example/gitwork.out.month-week.md:
--------------------------------------------------------------------------------
1 | 王小花的工作报告(2022-08-01 至 2022-08-31)
2 |
3 | - [1. 工作报告](#1-工作报告)
4 | - [1.1. 工作报告生成器](#11-工作报告生成器)
5 | - [1.1.1. 2022年08月](#111-2022年08月)
6 | - [1.1.1.1. 第3周](#1111-第3周)
7 | - [1.1.1.2. 第1周](#1112-第1周)
8 | - [1.2. markdown 转换工具](#12-markdown-转换工具)
9 | - [1.2.1. 2022年08月](#121-2022年08月)
10 | - [1.2.1.1. 第3周](#1211-第3周)
11 | - [1.2.1.2. 第2周](#1212-第2周)
12 | - [1.2.1.3. 第1周](#1213-第1周)
13 | - [1.3. 接口伴侣](#13-接口伴侣)
14 | - [1.3.1. 2022年08月](#131-2022年08月)
15 | - [1.3.1.1. 第3周](#1311-第3周)
16 | - [1.3.1.2. 第1周](#1312-第1周)
17 | - [2. 后续计划](#2-后续计划)
18 | - [2.1. 工作报告生成器](#21-工作报告生成器)
19 |
20 | ---
21 |
22 | 大家好,以下内容是我的工作报告和后续计划,不足之处敬请指正。
23 |
24 | ## 1. 工作报告
25 |
26 | 小结:在这个阶段中,主要精力集中在 `工作报告生成器` 这个项目,以及 `添加功能` 。 处理 `支持过滤掉相似度较高的 msg` 这项工作消耗了比预期较多的时间,主要原因是 `在实现功能上需要处理比较多的逻辑` 。
27 |
28 |
29 | ### 1.1. 工作报告生成器
30 | #### 1.1.1. 2022年08月
31 | ##### 1.1.1.1. 第3周
32 | - 🔧更新辅助工具: 屏蔽无关日志, 使用 readme 作为 help 输出
33 | - ✨添加功能: 更新默认模板
34 | - 🐛修复缺陷: git log 结果仅有一条时不应出现错误
35 |
36 | ```
37 | Error: Bad arguments: First argument should be a string, second should be an array of strings
38 | ```
39 |
40 | ##### 1.1.1.2. 第1周
41 | - ✨添加功能: 比较 commit msg 时忽略大小写
42 | - ✨添加功能: 支持 ignoreAuthor 参数, 是否忽略按用户名进行过滤
43 | - ✨添加功能: 使用 --branches 参数而不是 --all 参数
44 | - 🐛修复缺陷: 修复 --debug 参数失效
45 | - ✨添加功能: 从命令行覆盖配置项不再需要 -- 前缀
46 | - 🐛修复缺陷: 处理 --help 报错
47 | - 🐛修复缺陷: 需要显示当天内容
48 |
49 | - 例如当天是 2022-08-01, 并且当天有 commit, 但是运行 `after=2022-08-01 before=2022-08-31` 没有任何输出. 运行 `after=2022-07-31 before=2022-07-31` 也没有输出.
50 | - 应变更为包含当天
51 | - ✨添加功能: 转换 msg 提交标志
52 |
53 | 例如转换 `fix(client): xxx` 为 `修复 client 中的缺陷: xxx`
54 | - ✨添加功能: 支持过滤掉相似度较高的 msg
55 |
56 |
57 | ### 1.2. markdown 转换工具
58 | #### 1.2.1. 2022年08月
59 | ##### 1.2.1.1. 第3周
60 | - 📝更新文档: update readme
61 | - 🐛修复缺陷: 不应屏蔽 console.log, 因为源插件使用它输出错误
62 |
63 | ##### 1.2.1.2. 第2周
64 | - ✨添加功能: 简化输入参数
65 | - 🔧更新辅助工具: 更新文档, 修剪日志
66 | - ✨添加功能: 简化输入的命令
67 |
68 | - 前: extension.js cmd=extension.markdown-pdf.pdf
69 | - 后: extension.js out=pdf
70 | - ✨添加功能: 优化 input 参数合法性判断
71 | - ✨添加功能: 实现从命令行导出 pdf
72 |
73 | ``` sh
74 | extension.js cmd=extension.markdown-pdf.pdf input=D:/README.zh.md
75 | ```
76 |
77 | ##### 1.2.1.3. 第1周
78 | - ✨添加功能: 实现对象拦截, 目前验证 vscode 功能正常
79 | - ✨添加功能: 使用自己实现的方法简单实现拦截
80 | - ✨添加功能: 在 node 环境创建 vscode proxy
81 |
82 | 但是在 vscode 环境会导致 proxy 失败.
83 |
84 | 如果对 vscode 对象进行代理, 会报以下错误:
85 | ``` txt
86 | TypeError: 'get' on proxy: property 'getText' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected 'getText(p){return p?w._getTextInRange(p):w.getText()}' but got 'getText(p){return p?w._getTextInRange(p):w.getText()}')
87 | ```
88 | - 🔧更新辅助工具: 添加 vsce package 和 npm build 命令
89 |
90 |
91 | ### 1.3. 接口伴侣
92 | #### 1.3.1. 2022年08月
93 | ##### 1.3.1.1. 第3周
94 | - 🐛修复缺陷: 避免 config.openApi 配置为空数组时出现读取错误
95 |
96 | ##### 1.3.1.2. 第1周
97 | - ✨添加功能: 调整一些依赖为可选项
98 |
99 | 注: yarn install 使用 --ignore-optional 或 --production 时依然会请求可选项中的依赖表文件
100 | - ✨添加功能: 把动态依赖放置于 optionalDependencies
101 |
102 | 因为动态安装经常不太稳定, 如果放置在可选依赖中则可以在第一次安装时尝试安装它们, 就算安装失败了也没有关系
103 | - 🐛修复缺陷: hasPackage 不应只检测子级 node_modules
104 |
105 | 例如全局安装时, npm 会把 node_modules 进行扁平化, 例如 md-cli 依赖了 mockm, 安装 md-cli 时, 只会存在 md-cli/node_modules 而不会存在 md-cli/node_modules/mockm/node_modules .
106 |
107 |
108 | ## 2. 后续计划
109 | ### 2.1. 工作报告生成器
110 | - [ ] feat: 支持生成 html/pdf/jpeg...
111 | - [ ] feat: 支持作为邮件自动发送
--------------------------------------------------------------------------------
/example/gitwork.out.week.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | gitwork.out.week.md
5 |
6 |
7 |
228 |
229 |
304 |
305 |
356 |
357 |
358 |
359 |
360 |
368 | 王小花的工作报告(2022-08-01 至 2022-08-21)
369 |
399 |
400 | 大家好,以下内容是我的工作报告和后续计划,不足之处敬请指正。
401 | 1. 工作报告
402 | 小结:在这个阶段中,主要精力集中在 工作报告生成器
这个项目,以及 添加功能
。 处理 支持过滤掉相似度较高的 msg
这项工作消耗了比预期较多的时间,主要原因是 在实现功能上需要处理比较多的逻辑
。
403 | 1.1. 工作报告生成器
404 | 1.1.1. 2022年08月 第3周
405 |
406 | -
407 |
🔧更新辅助工具: 屏蔽无关日志, 使用 readme 作为 help 输出
408 |
409 | -
410 |
✨添加功能: 更新默认模板
411 |
412 | -
413 |
🐛修复缺陷: git log 结果仅有一条时不应出现错误
414 | Error: Bad arguments: First argument should be a string, second should be an array of strings
415 |
416 |
417 |
418 | 1.1.2. 2022年08月 第1周
419 |
420 | -
421 |
✨添加功能: 比较 commit msg 时忽略大小写
422 |
423 | -
424 |
✨添加功能: 支持 ignoreAuthor 参数, 是否忽略按用户名进行过滤
425 |
426 | -
427 |
✨添加功能: 使用 --branches 参数而不是 --all 参数
428 |
429 | -
430 |
🐛修复缺陷: 修复 --debug 参数失效
431 |
432 | -
433 |
✨添加功能: 从命令行覆盖配置项不再需要 -- 前缀
434 |
435 | -
436 |
🐛修复缺陷: 处理 --help 报错
437 |
438 | -
439 |
🐛修复缺陷: 需要显示当天内容
440 |
441 | - 例如当天是 2022-08-01, 并且当天有 commit, 但是运行
after=2022-08-01 before=2022-08-31
没有任何输出. 运行 after=2022-07-31 before=2022-07-31
也没有输出.
442 | - 应变更为包含当天
443 |
444 |
445 | -
446 |
✨添加功能: 转换 msg 提交标志
447 | 例如转换 fix(client): xxx
为 修复 client 中的缺陷: xxx
448 |
449 | -
450 |
✨添加功能: 支持过滤掉相似度较高的 msg
451 |
452 |
453 | 1.2. markdown 转换工具
454 | 1.2.1. 2022年08月 第3周
455 |
456 | - 📝更新文档: update readme
457 | - 🐛修复缺陷: 不应屏蔽 console.log, 因为源插件使用它输出错误
458 |
459 | 1.2.2. 2022年08月 第2周
460 |
483 | 1.2.3. 2022年08月 第1周
484 |
485 | -
486 |
✨添加功能: 实现对象拦截, 目前验证 vscode 功能正常
487 |
488 | -
489 |
✨添加功能: 使用自己实现的方法简单实现拦截
490 |
491 | -
492 |
✨添加功能: 在 node 环境创建 vscode proxy
493 | 但是在 vscode 环境会导致 proxy 失败.
494 | 如果对 vscode 对象进行代理, 会报以下错误:
495 | TypeError: 'get' on proxy: property 'getText' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected 'getText(p){return p?w._getTextInRange(p):w.getText()}' but got 'getText(p){return p?w._getTextInRange(p):w.getText()}')
496 |
497 |
498 | -
499 |
🔧更新辅助工具: 添加 vsce package 和 npm build 命令
500 |
501 |
502 | 1.3. 接口伴侣
503 | 1.3.1. 2022年08月 第3周
504 |
505 | - 🐛修复缺陷: 避免 config.openApi 配置为空数组时出现读取错误
506 |
507 | 1.3.2. 2022年08月 第1周
508 |
509 | -
510 |
✨添加功能: 调整一些依赖为可选项
511 | 注: yarn install 使用 --ignore-optional 或 --production 时依然会请求可选项中的依赖表文件
512 |
513 | -
514 |
✨添加功能: 把动态依赖放置于 optionalDependencies
515 | 因为动态安装经常不太稳定, 如果放置在可选依赖中则可以在第一次安装时尝试安装它们, 就算安装失败了也没有关系
516 |
517 | -
518 |
🐛修复缺陷: hasPackage 不应只检测子级 node_modules
519 | 例如全局安装时, npm 会把 node_modules 进行扁平化, 例如 md-cli 依赖了 mockm, 安装 md-cli 时, 只会存在 md-cli/node_modules 而不会存在 md-cli/node_modules/mockm/node_modules .
520 |
521 |
522 | 2. 后续计划
523 | 2.1. 工作报告生成器
524 |
528 |
529 |
530 |
531 |
--------------------------------------------------------------------------------
/example/gitwork.out.week.md:
--------------------------------------------------------------------------------
1 | 王小花的工作报告(2022-08-01 至 2022-08-21)
2 |
3 | - [1. 工作报告](#1-工作报告)
4 | - [1.1. 工作报告生成器](#11-工作报告生成器)
5 | - [1.1.1. 2022年08月 第3周](#111-2022年08月-第3周)
6 | - [1.1.2. 2022年08月 第1周](#112-2022年08月-第1周)
7 | - [1.2. markdown 转换工具](#12-markdown-转换工具)
8 | - [1.2.1. 2022年08月 第3周](#121-2022年08月-第3周)
9 | - [1.2.2. 2022年08月 第2周](#122-2022年08月-第2周)
10 | - [1.2.3. 2022年08月 第1周](#123-2022年08月-第1周)
11 | - [1.3. 接口伴侣](#13-接口伴侣)
12 | - [1.3.1. 2022年08月 第3周](#131-2022年08月-第3周)
13 | - [1.3.2. 2022年08月 第1周](#132-2022年08月-第1周)
14 | - [2. 后续计划](#2-后续计划)
15 | - [2.1. 工作报告生成器](#21-工作报告生成器)
16 |
17 | ---
18 |
19 | 大家好,以下内容是我的工作报告和后续计划,不足之处敬请指正。
20 |
21 |
22 | ## 1. 工作报告
23 |
24 | 小结:在这个阶段中,主要精力集中在 `工作报告生成器` 这个项目,以及 `添加功能` 。 处理 `支持过滤掉相似度较高的 msg` 这项工作消耗了比预期较多的时间,主要原因是 `在实现功能上需要处理比较多的逻辑` 。
25 |
26 |
27 | ### 1.1. 工作报告生成器
28 | #### 1.1.1. 2022年08月 第3周
29 | - 🔧更新辅助工具: 屏蔽无关日志, 使用 readme 作为 help 输出
30 | - ✨添加功能: 更新默认模板
31 | - 🐛修复缺陷: git log 结果仅有一条时不应出现错误
32 |
33 | ```
34 | Error: Bad arguments: First argument should be a string, second should be an array of strings
35 | ```
36 |
37 | #### 1.1.2. 2022年08月 第1周
38 | - ✨添加功能: 比较 commit msg 时忽略大小写
39 | - ✨添加功能: 支持 ignoreAuthor 参数, 是否忽略按用户名进行过滤
40 | - ✨添加功能: 使用 --branches 参数而不是 --all 参数
41 | - 🐛修复缺陷: 修复 --debug 参数失效
42 | - ✨添加功能: 从命令行覆盖配置项不再需要 -- 前缀
43 | - 🐛修复缺陷: 处理 --help 报错
44 | - 🐛修复缺陷: 需要显示当天内容
45 |
46 | - 例如当天是 2022-08-01, 并且当天有 commit, 但是运行 `after=2022-08-01 before=2022-08-31` 没有任何输出. 运行 `after=2022-07-31 before=2022-07-31` 也没有输出.
47 | - 应变更为包含当天
48 | - ✨添加功能: 转换 msg 提交标志
49 |
50 | 例如转换 `fix(client): xxx` 为 `修复 client 中的缺陷: xxx`
51 | - ✨添加功能: 支持过滤掉相似度较高的 msg
52 |
53 |
54 | ### 1.2. markdown 转换工具
55 | #### 1.2.1. 2022年08月 第3周
56 | - 📝更新文档: update readme
57 | - 🐛修复缺陷: 不应屏蔽 console.log, 因为源插件使用它输出错误
58 |
59 | #### 1.2.2. 2022年08月 第2周
60 | - ✨添加功能: 简化输入参数
61 | - 🔧更新辅助工具: 更新文档, 修剪日志
62 | - ✨添加功能: 简化输入的命令
63 |
64 | - 前: extension.js cmd=extension.markdown-pdf.pdf
65 | - 后: extension.js out=pdf
66 | - ✨添加功能: 优化 input 参数合法性判断
67 | - ✨添加功能: 实现从命令行导出 pdf
68 |
69 | ``` sh
70 | extension.js cmd=extension.markdown-pdf.pdf input=D:/README.zh.md
71 | ```
72 |
73 | #### 1.2.3. 2022年08月 第1周
74 | - ✨添加功能: 实现对象拦截, 目前验证 vscode 功能正常
75 | - ✨添加功能: 使用自己实现的方法简单实现拦截
76 | - ✨添加功能: 在 node 环境创建 vscode proxy
77 |
78 | 但是在 vscode 环境会导致 proxy 失败.
79 |
80 | 如果对 vscode 对象进行代理, 会报以下错误:
81 | ``` txt
82 | TypeError: 'get' on proxy: property 'getText' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected 'getText(p){return p?w._getTextInRange(p):w.getText()}' but got 'getText(p){return p?w._getTextInRange(p):w.getText()}')
83 | ```
84 | - 🔧更新辅助工具: 添加 vsce package 和 npm build 命令
85 |
86 |
87 | ### 1.3. 接口伴侣
88 | #### 1.3.1. 2022年08月 第3周
89 | - 🐛修复缺陷: 避免 config.openApi 配置为空数组时出现读取错误
90 |
91 | #### 1.3.2. 2022年08月 第1周
92 | - ✨添加功能: 调整一些依赖为可选项
93 |
94 | 注: yarn install 使用 --ignore-optional 或 --production 时依然会请求可选项中的依赖表文件
95 | - ✨添加功能: 把动态依赖放置于 optionalDependencies
96 |
97 | 因为动态安装经常不太稳定, 如果放置在可选依赖中则可以在第一次安装时尝试安装它们, 就算安装失败了也没有关系
98 | - 🐛修复缺陷: hasPackage 不应只检测子级 node_modules
99 |
100 | 例如全局安装时, npm 会把 node_modules 进行扁平化, 例如 md-cli 依赖了 mockm, 安装 md-cli 时, 只会存在 md-cli/node_modules 而不会存在 md-cli/node_modules/mockm/node_modules .
101 |
102 |
103 | ## 2. 后续计划
104 | ### 2.1. 工作报告生成器
105 | - [ ] feat: 支持生成 html/pdf/jpeg...
106 | - [ ] feat: 支持作为邮件自动发送
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | console._log = console.log
4 | console.log = () => {}
5 | const util = require(`./util.js`)
6 | util.init()
7 | const {
8 | parseArgv,
9 | execSync,
10 | help,
11 | paserLogToList,
12 | toMd,
13 | } = util
14 | const argv = parseArgv()
15 | const cli = {
16 | ...argv,
17 | '--debug': argv[`--debug`] || false,
18 | '--select': argv[`--select`] ? argv[`--select`].split(`,`) : [`default`],
19 | }
20 |
21 | { // 关联参数特殊处理
22 | if(cli['--help'] || cli['-h']) {
23 | help()
24 | process.exit()
25 | }
26 | if(cli['--config']) {
27 | util.opener(GET(`configdir`))
28 | process.exit()
29 | }
30 | if(cli['-v'] || cli['--version']) {
31 | util.print(GET(`package`).version)
32 | process.exit()
33 | }
34 | }
35 |
36 | SET(`cli`, cli)
37 |
38 | const config = GET(`config`)
39 | let reportList = config.report.filter(item => cli[`--select`].includes(item.select))
40 | if(reportList.length === 0) {
41 | util.print(`所指定的 --select 值不存在, 将使用默认值`)
42 | reportList = config.report.filter(item => [`default`].includes(item.select))
43 | }
44 | reportList.forEach(reportItem => {
45 | const newReportItem = util.handleReportConfig({reportItem, query: cli})
46 | SET(`curReport`, newReportItem)
47 | const repository = newReportItem.repository.map(repositoryItem => {
48 | let cmdRes = ``
49 | try {
50 | cmdRes = execSync(
51 | util.logLine(newReportItem),
52 | {
53 | cwd: repositoryItem.path,
54 | }
55 | )
56 | } catch (error) {
57 | util.errExit(`获取 git 工作记录失败`, error)
58 | }
59 | const list = paserLogToList({str: cmdRes})
60 | const toMdRes = toMd({
61 | list,
62 | rootLevel: newReportItem.rootLevel,
63 | })
64 | const obj = {
65 | name: `${`#`.repeat(util.getTitleLevel({type: `repository`, rootLevel: newReportItem.rootLevel}))} ${repositoryItem.name}`,
66 | toMdRes
67 | }
68 | return [
69 | obj.name,
70 | obj.toMdRes,
71 | `\n`,
72 | ].join(`\n`)
73 | }).join(`\n`).trim()
74 | const out = util.handleTemplateFile({report: reportItem, body: repository})
75 | require(`fs`).writeFileSync(newReportItem.outFile, out)
76 | })
--------------------------------------------------------------------------------
/lib/opener.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | // from: https://unpkg.com/opener@1.5.1/lib/opener.js
4 |
5 | var childProcess = require("child_process");
6 | var os = require("os");
7 |
8 | module.exports = function opener(args, options, callback) {
9 | var platform = process.platform;
10 |
11 | // Attempt to detect Windows Subystem for Linux (WSL). WSL itself as Linux (which works in most cases), but in
12 | // this specific case we need to treat it as actually being Windows. The "Windows-way" of opening things through
13 | // cmd.exe works just fine here, whereas using xdg-open does not, since there is no X Windows in WSL.
14 | if (platform === "linux" && os.release().indexOf("Microsoft") !== -1) {
15 | platform = "win32";
16 | }
17 |
18 | // http://stackoverflow.com/q/1480971/3191, but see below for Windows.
19 | var command;
20 | switch (platform) {
21 | case "win32": {
22 | command = "cmd.exe";
23 | break;
24 | }
25 | case "darwin": {
26 | command = "open";
27 | break;
28 | }
29 | default: {
30 | command = "xdg-open";
31 | break;
32 | }
33 | }
34 |
35 | if (typeof args === "string") {
36 | args = [args];
37 | }
38 |
39 | if (typeof options === "function") {
40 | callback = options;
41 | options = {};
42 | }
43 |
44 | if (options && typeof options === "object" && options.command) {
45 | if (platform === "win32") {
46 | // *always* use cmd on windows
47 | args = [options.command].concat(args);
48 | } else {
49 | command = options.command;
50 | }
51 | }
52 |
53 | if (platform === "win32") {
54 | // On Windows, we really want to use the "start" command. But, the rules regarding arguments with spaces, and
55 | // escaping them with quotes, can get really arcane. So the easiest way to deal with this is to pass off the
56 | // responsibility to "cmd /c", which has that logic built in.
57 | //
58 | // Furthermore, if "cmd /c" double-quoted the first parameter, then "start" will interpret it as a window title,
59 | // so we need to add a dummy empty-string window title: http://stackoverflow.com/a/154090/3191
60 | //
61 | // Additionally, on Windows ampersand needs to be escaped when passed to "start"
62 | args = args.map(function (value) {
63 | return value.replace(/&/g, "^&");
64 | });
65 | args = ["/c", "start", "\"\""].concat(args);
66 | }
67 |
68 | return childProcess.execFileSync(command, args, options, callback);
69 | };
70 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gitwork",
3 | "version": "2.0.6",
4 | "description": "",
5 | "bin": {
6 | "gitwork": "index.js"
7 | },
8 | "scripts": {
9 | "dev": "node index.js template=week messageBody=none after=2022-06-01 before=2022-07-31 --select=default"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git://github.com/wll8/gitday.git"
14 | },
15 | "homepage": "https://wll8.github.io/gitday/",
16 | "keywords": [],
17 | "author": "xw",
18 | "license": "ISC",
19 | "dependencies": {
20 | "lodash.unionby": "^4.8.0",
21 | "moment": "^2.29.4",
22 | "mustache": "^4.2.0",
23 | "opener": "^1.5.2",
24 | "string-similarity": "^4.0.4"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/util.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const os = require('os')
3 | const cp = require('child_process')
4 | const path = require('path')
5 | const moment = require('moment')
6 | const mustache = require('mustache')
7 | mustache.escape = text => text // 不转义字符
8 | const stringSimilarity = require('string-similarity')
9 | const opener = require('./lib/opener.js')
10 | const unionby = require('lodash.unionby')
11 | const pkg = require(`./package.json`)
12 |
13 |
14 | function removeLeft(str) {
15 | const lines = str.split(`\n`)
16 | // 获取应该删除的空白符数量
17 | const minSpaceNum = lines.filter(item => item.trim())
18 | .map(item => item.match(/(^\s+)?/)[0].length)
19 | .sort((a, b) => a - b)[0]
20 | // 删除空白符
21 | const newStr = lines
22 | .map(item => item.slice(minSpaceNum))
23 | .join(`\n`)
24 | return newStr
25 | }
26 |
27 | function print(...arg) {
28 | return console._log(...arg)
29 | }
30 |
31 | /**
32 | * 获取所使用的 git log 命令
33 | * @returns string
34 | */
35 | function logLine({after, before}) {
36 | // 由于 git --before 包含当天, 但 --after 不包含当天, 所以 after 需要再推前
37 | const newAfter = moment(after).add({day: -1}).format(`YYYY-MM-DD`)
38 | const str = [
39 | `git`,
40 | `log`,
41 | `--branches`,
42 | `--after=${newAfter}`,
43 | `--before=${before}`,
44 | `--no-merges`,
45 | `--format=fuller`,
46 | ].join(` `)
47 | return str
48 | }
49 |
50 | function help() {
51 | print(fs.readFileSync(`${__dirname}/README.md`, `utf8`))
52 | print(`\n访问 ${pkg.homepage} 查看详情`)
53 | // opener(pkg.homepage)
54 | }
55 |
56 | function execSync(cmd, option) {
57 | console.log(`>`, cmd)
58 | return cp.execSync(cmd, option).toString().trim()
59 | }
60 |
61 | function dateFormater(t, formater) { // 时间格式化
62 | let date = t ? new Date(t) : new Date()
63 | return moment(date).format(formater)
64 | }
65 |
66 | function parseArgv(arr) { // 解析命令行参数
67 | return (arr || process.argv.slice(2)).reduce((acc, arg) => {
68 | let [k, ...v] = arg.split(`=`)
69 | v = v.join(`=`) // 把带有 = 的值合并为字符串
70 | acc[k] = v === `` // 没有值时, 则表示为 true
71 | ? true
72 | : (
73 | /^(true|false)$/.test(v) // 转换指明的 true/false
74 | ? v === `true`
75 | : (
76 | /[\d|.]+/.test(v)
77 | ? (isNaN(Number(v)) ? v : Number(v)) // 如果转换为数字失败, 则使用原始字符
78 | : v
79 | )
80 | )
81 | return acc
82 | }, {})
83 | }
84 |
85 | function handleMsg(rawMsg) {
86 | let msg = ((rawMsg.match(/(\n\n)([\s\S]+?$)/) || [])[2] || ``)
87 | msg = removeLeft(msg)
88 | let [, one, body] = msg.trim().match(/(.*)[\n]?([\s\S]*)/)
89 | one = one.trim()
90 | body = body.trim()
91 | one = handleOneMsg(one)
92 | let newMsg = ``
93 | if(GET(`curReport`).messageBody === `none`) { // 不使用 body
94 | newMsg = one
95 | }
96 | if(GET(`curReport`).messageBody === `compatible`) { // 当 body 含有可能破坏报告的内容时不使用
97 | if(
98 | body.match(/^#{1,6}\s+/gm) // 含有 # 标题
99 | ) {
100 | newMsg = one
101 | } else {
102 | newMsg = `${one}\n${body}`
103 | }
104 | }
105 | if(GET(`curReport`).messageBody === `raw`) { // 原样使用 body
106 | newMsg = `${one}\n${body}`
107 | }
108 | newMsg = newMsg.trim()
109 | return newMsg
110 | }
111 |
112 | /**
113 | * 根据 git commit 规范转换 msg 风格
114 | */
115 | function handleOneMsg(msg) {
116 | const {type, scope, subject} = parseMsg(msg)
117 | let newMsg = msg
118 | const [key, val] = Object.entries(GET(`curReport`).messageConvert).find(([key, val]) => {
119 | const {rating} = stringSimilarity.findBestMatch(type.toLocaleLowerCase(), [key, ...val.alias].map(item => item.toLocaleLowerCase())).bestMatch
120 | return (rating > GET(`curReport`).messageTypeSimilarity)
121 | }) || []
122 | if(key) {
123 | const template = scope ? val.des.text[1] : val.des.text[0]
124 | const text = render( [template, subject].join(``), {scope})
125 | const emoji = val.des.emoji
126 | const newText = render(GET(`curReport`).messageTypeTemplate, {text, emoji})
127 | newMsg = newText
128 | }
129 | return newMsg
130 | }
131 |
132 | /**
133 | * 解析 git msg 中的 type scope subject
134 | * arg0 string
135 | * @param {*} msg
136 | * @returns
137 | */
138 | function parseMsg(msg) {
139 | let type, scope, subject
140 | ;[, type = ``, subject = ``] = msg.trim().match(/(.+?)[::][\s+](.*$)/) || []
141 | ;[, type = ``, scope = ``] = type.trim().match(/\s{0}(.+?)\s{0}\(\s{0,}(.*)\s{0,}\)$/) || [, type]
142 | type = type.trim()
143 | scope = scope.trim()
144 | subject = subject.trim()
145 | const res = {type, scope, subject}
146 | return res
147 | }
148 |
149 | /**
150 | * 把 git log 的内容解析为数组
151 | * @param {*} str
152 | * @returns
153 | */
154 | function paserLogToList({str}) {
155 | const authorList = GET(`curReport`).author || []
156 | str = `\n${str}`
157 | const tag = /\ncommit /
158 | let list = str.split(tag).filter(item => item.trim()).map(rawMsg => {
159 | rawMsg = `commit ${rawMsg}`
160 | const obj = {}
161 | obj.raw = rawMsg
162 | // 使用 CommitDate 而不是 AuthorDate
163 | // CommitDate: 被再次修改的时间或作为补丁使用的时间
164 | obj.date = (dateFormater((rawMsg.match(/CommitDate:(.*)/) || [])[1].trim(), 'YYYY-MM-DD HH:mm:ss'))
165 | // commit sha
166 | obj.commit = (rawMsg.match(/(.*)\n/) || [])[1].trim()
167 | // 作者及邮箱
168 | obj.author = (rawMsg.match(/Author:(.*)/) || [])[1].trim()
169 | obj.msg = handleMsg(rawMsg)
170 | return obj
171 | }).filter(item => {
172 | return (
173 | ( // 时间范围
174 | moment(item.date).isSameOrBefore(GET(`curReport`).before)
175 | && moment(item.date).isSameOrAfter(GET(`curReport`).after)
176 | )
177 | && ( // 作者
178 | GET(`curReport`).ignoreAuthor ||
179 | authorList.some(author => {
180 | return item.author.startsWith(`${author} <`)
181 | } )
182 | )
183 | )
184 | })
185 | // 去除重复的 msg
186 | GET(`curReport`).noEqualMsg && (list = unionby(list, `msg`));
187 | // 去除大于指定相似度的 msg
188 | GET(`curReport`).similarity && (list = list = removeSimilarity({list, key: `msg`, similarity: GET(`curReport`).similarity}));
189 | return list
190 | }
191 |
192 | /**
193 | * 删除数组中指定字段的相似度大于指定值的数组
194 | * arg.list 提供的数组
195 | * arg.key 要比较相似度的字段
196 | * arg.similarity 相似值
197 | */
198 | function removeSimilarity({list, key = `msg`, similarity = 0.75}) {
199 | if(list.length < 2) {
200 | return list
201 | }
202 | let msgList = list.map(item => item[key])
203 | let res = []
204 | msgList.forEach((msg, index) => {
205 | let newList = [...msgList]
206 | newList.splice(index, 1)
207 | const diff = stringSimilarity.findBestMatch(msg, newList)
208 | .ratings
209 | .filter(item => {
210 | return item.rating > similarity
211 | }).map(item => item.target)
212 | const select = diff.length ? diff[0] : msg
213 | res = res.includes(select) ? res : res.concat(msg)
214 | })
215 | const newList = list.filter(item => res.includes(item[key]))
216 | return newList
217 | }
218 |
219 |
220 | /**
221 | * 根据指定的 key 排序
222 | * @param {boolean} showNew false 降序 true 升序
223 | * @param {string} key 要排序的 key
224 | * @returns function
225 | */
226 | function sort(showNew, key) {
227 | return (a, b) => showNew ? (b[key] - a[key]) : (a[key] - b[key])
228 | }
229 |
230 | /**
231 | * 根据 title 标记输出 md
232 | * @param {object} param0
233 | * @param {array} param0.list log 数据
234 | * @returns string
235 | */
236 | function create({list, rootLevel}) {
237 | const template = GET(`curReport`).template
238 | const titleMap = {
239 | 'month': [
240 | '@{year}年@{month}月',
241 | ],
242 | 'month-week': [
243 | '@{year}年@{month}月',
244 | '第@{week}周',
245 | ],
246 | 'month-week-day': [
247 | '@{year}年@{month}月',
248 | '第@{week}周',
249 | '@{day}日 星期@{weekDay}',
250 | ],
251 | 'week': [
252 | '@{year}年@{month}月 第@{week}周',
253 | ],
254 | 'week-day': [
255 | '@{year}年@{month}月 第@{week}周',
256 | '@{day}日 星期@{weekDay}',
257 | ],
258 | 'day': [
259 | '@{year}年@{month}月@{day}日',
260 | ],
261 | }
262 | let titleList = titleMap[template]
263 | if(!titleList) {
264 | new Error(`不支持的标记`)
265 | return process.exit()
266 | } else { // 添加 # 标题标志
267 | titleList = titleList.map((item, index) => {
268 | const level = getTitleLevel({type: `date`, rootLevel}) + index
269 | return `${`#`.repeat(level)} ${item}`
270 | })
271 | }
272 | let oldTitle = []
273 | const str = list.map(item => {
274 | let titleStr = []
275 | const newTitle = titleList.map((title, index) => {
276 | // 模拟一段代码实现简单的模板语法
277 | title = render(`\n${title}`, item.dateObj)
278 | titleStr[index] = oldTitle[index] === title ? `` : title
279 | return title
280 | } )
281 | const res = [
282 | titleStr.join(``),
283 | `\n- ${debug({item})}${ // 多行 msg 的时候在行前面加空格, 以处理缩进关系
284 | item.msg.split(`\n`).map((msgLine) => ` ${msgLine}`).join(`\n`).trim()
285 | }`
286 | ].filter(item => item).join(`\n`)
287 | oldTitle = newTitle
288 | return res
289 | }).join(`\n`).trim()
290 | return str
291 | }
292 |
293 | function debug({item}) {
294 | if(GET(`cli`)[`--debug`]) {
295 | return `${item.date} ${item.commit} ${item.author}\n `
296 | } else {
297 | return ``
298 | }
299 | }
300 |
301 | /**
302 | * 根据标志对应的时间转换为 markdown 格式
303 | * @param {object} param0
304 | * @param {string} [param0.tag = day] - 标记
305 | * @param {array} param0.list - 数据
306 | * @param {boolean} [param0.showNew = true] - 是否把新时间排到前面
307 | */
308 | function toMd({
309 | list = [],
310 | showNew = true,
311 | rootLevel = 1,
312 | }){
313 | list = list.map(obj => { // 先把每个类型的时间取出来方便使用
314 | const date = new Date(obj.date)
315 | obj.timeStamp = date.getTime()
316 | obj.dateObj = {
317 | year: dateFormater(obj.date, `YYYY`),
318 | month: dateFormater(obj.date, `MM`),
319 | day: dateFormater(obj.date, `DD`),
320 | week: Math.ceil((date.getDate() + 6 - date.getDay()) / 7), // 第几周
321 | weekDay: [`日`, `一`, `二`, `三`, `四`, `五`, `六`][date.getDay()], // 星期几
322 | }
323 | return obj
324 | }).sort(sort(showNew, `timeStamp`))
325 | const res = create({
326 | list,
327 | rootLevel,
328 | })
329 | return res
330 | }
331 |
332 | /**
333 | * 获取模板对应的时间
334 | */
335 | function handleLogTime({template}) {
336 | const tag = template.split(`-`).shift() // month week day
337 | // 处理 moment 是以周日作为每周的开始, 但现实中是以周一作为开始
338 | const offset = tag === `week` ? {day: 1} : {}
339 | const after = moment().startOf(tag).add(offset).format(`YYYY-MM-DD`)
340 | const before = moment().endOf(tag).add(offset).format(`YYYY-MM-DD`)
341 | return {after, before}
342 | }
343 |
344 | /**
345 | * 获取标题级别
346 | * 假设 rootLevel = 1, 效果为:
347 | * # 文档名称
348 | * ## 项目名称
349 | * ### 时间级别1
350 | * #### 时间级别2
351 | */
352 | function getTitleLevel({type, rootLevel = 1}) {
353 | return {
354 | repository: rootLevel + 1,
355 | date: rootLevel + 2,
356 | }[type]
357 | }
358 |
359 | /**
360 | * 模板处理器
361 | * report 报告配置
362 | */
363 | function handleTemplateFile({report, body}) {
364 | const insertBody = render(``, GET(`curReport`))
365 | const useFilePath = `${GET(`configdir`)}/${report.useFile}`
366 | const useFilePathDefault = `${GET(`configdir`)}/default.template.md`
367 | let mdBody = fs.existsSync(useFilePath) ? fs.readFileSync(useFilePath, `utf8`) : (print(`所指定的 useFile 值不存在, 将使用默认值`), fs.readFileSync(useFilePathDefault, `utf8`))
368 | mdBody = render(mdBody, GET(`curReport`))
369 | mdBody = mdBody.replace(/([\s\S]+?)/, ($0, $1) => (body || $1))
370 | mdBody = mdBody.replace(/([\s\S]+?)/, ($0, $1) => (insertBody || $1))
371 | return mdBody
372 | }
373 |
374 | /**
375 | * 渲染模板
376 | * @param {*} template
377 | * @returns
378 | */
379 | function render(template, view) {
380 | return mustache.render(template, view, {}, [`@{`, `}`])
381 | }
382 |
383 | /**
384 | * 处理报告配置,例如使用命令行参数覆盖报告中的配置
385 | */
386 | function handleReportConfig({reportItem: cfg, query: cli}) {
387 | const curPath = process.cwd()
388 | const curPathName = path.parse(curPath).name
389 | const newReport = {
390 | // 默认值
391 | ...GET(`reportDefault`),
392 | // 配置值
393 | ...cfg,
394 | // 命令行值
395 | ...Object.entries(cli).reduce((acc, [key, val]) => ({...acc, [key]: val}), {})
396 | }
397 | // 根据 template 预处理时间
398 | newReport.after = newReport.after || handleLogTime({template: newReport.template}).after
399 | newReport.before = newReport.before || handleLogTime({template: newReport.template}).before
400 |
401 | newReport.author = cli[`author`]
402 | ? cli[`author`].split(`,`)
403 | : (
404 | (cfg.author && cfg.author.length)
405 | ? cfg.author
406 | : [getDefaultGitName()]
407 | );
408 |
409 | newReport.authorName = newReport.authorName || newReport.author[0]
410 | newReport.repository = cli[`repository`]
411 | ? cli[`author`].split(`,`).map(item => ({path: item, name: path.parse(item).name}))
412 | : (
413 | (cfg.repository && cfg.repository.length)
414 | ? cfg.repository
415 | : [{path: curPath, name: curPathName}]
416 | );
417 | return newReport
418 | }
419 |
420 | function getDefaultGitName() {
421 | try {
422 | return execSync(`git config user.name`)
423 | } catch (error) {
424 | errExit(`获取 git 的默认用户名失败`, error)
425 | }
426 | }
427 |
428 | function errExit(msg, error) {
429 | print(msg)
430 | print(String(error))
431 | process.exit()
432 | }
433 |
434 | /**
435 | * 初始化程序
436 | */
437 | function init() {
438 | global.SET = (key, val) => {
439 | console.log(`SET`, key, val)
440 | global[`${pkg.name}_${key}`] = val
441 | return val
442 | }
443 | global.GET = (key) => {
444 | return global[`${pkg.name}_${key}`]
445 | }
446 | global.SET(`package`, pkg)
447 | const configdir = `${os.homedir()}/.${pkg.name}/`
448 | global.SET(`configdir`, configdir)
449 | global.SET(`configFilePath`, `${configdir}/config.js`)
450 | global.SET(`reportDefault`, require(`./config/config.js`).report.find(item => item.select === `default`))
451 | if(fs.existsSync(configdir) === false) { // 创建配置文件目录
452 | fs.mkdirSync(configdir, {recursive: true})
453 | }
454 |
455 | // 如果文件不存在, 则创建它们
456 | [
457 | [`${__dirname}/config/config.js`, GET(`configFilePath`)],
458 | [`${__dirname}/config/default.template.md`, `${GET(`configdir`)}/default.template.md`],
459 | ].forEach(([form, to]) => {
460 | if(
461 | (fs.existsSync(to) === false)
462 | || (fs.readFileSync(to, `utf8`).trim() === ``)
463 | ) { // 创建配置文件
464 | fs.copyFileSync(form, to)
465 | }
466 | })
467 | global.SET(`config`, require(GET(`configFilePath`)))
468 | }
469 |
470 | module.exports = {
471 | init,
472 | opener,
473 | errExit,
474 | handleReportConfig,
475 | handleTemplateFile,
476 | getTitleLevel,
477 | handleLogTime,
478 | logLine,
479 | toMd,
480 | create,
481 | sort,
482 | help,
483 | print,
484 | removeLeft,
485 | execSync,
486 | parseArgv,
487 | paserLogToList,
488 | }
--------------------------------------------------------------------------------
/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | lodash.unionby@^4.8.0:
6 | version "4.8.0"
7 | resolved "https://registry.npmmirror.com/lodash.unionby/-/lodash.unionby-4.8.0.tgz#883f098ff78f564a727b7508e09cdd539734bb83"
8 | integrity sha512-e60kn4GJIunNkw6v9MxRnUuLYI/Tyuanch7ozoCtk/1irJTYBj+qNTxr5B3qVflmJhwStJBv387Cb+9VOfABMg==
9 |
10 | moment@^2.29.4:
11 | version "2.29.4"
12 | resolved "https://registry.npmmirror.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108"
13 | integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==
14 |
15 | mustache@^4.2.0:
16 | version "4.2.0"
17 | resolved "https://registry.npmmirror.com/mustache/-/mustache-4.2.0.tgz#e5892324d60a12ec9c2a73359edca52972bf6f64"
18 | integrity sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==
19 |
20 | opener@^1.5.2:
21 | version "1.5.2"
22 | resolved "https://registry.npmmirror.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598"
23 | integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==
24 |
25 | string-similarity@^4.0.4:
26 | version "4.0.4"
27 | resolved "https://registry.npmmirror.com/string-similarity/-/string-similarity-4.0.4.tgz#42d01ab0b34660ea8a018da8f56a3309bb8b2a5b"
28 | integrity sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ==
29 |
--------------------------------------------------------------------------------