|
| 1 | +''' |
| 2 | +Author: LetMeFly |
| 3 | +Date: 2022-07-03 11:21:14 |
| 4 | +LastEditors: LetMeFly.xyz |
| 5 | +LastEditTime: 2025-04-06 21:22:59 |
| 6 | +Command: python newSolution.py 102. 二叉树的层序遍历 |
| 7 | +What's more: 当前仅支持数字开头的题目 |
| 8 | +What's more: 代码结构写的很混乱 - 想单文件实现所有操作 |
| 9 | +''' |
| 10 | +import os |
| 11 | +import re |
| 12 | +import sys |
| 13 | +import json |
| 14 | +import time |
| 15 | +import datetime |
| 16 | +import subprocess |
| 17 | +from urllib.parse import quote |
| 18 | + |
| 19 | + |
| 20 | +argv = sys.argv |
| 21 | +print(argv) |
| 22 | + |
| 23 | +num = int(argv[1][:-1]) |
| 24 | +title = "" |
| 25 | +for i in range(2, len(argv)): |
| 26 | + if i != 2: |
| 27 | + title += " " |
| 28 | + title += argv[i] |
| 29 | +nameProblem = "AllProblems/{0}.{1}.md".format(num, title) |
| 30 | +print(nameProblem) |
| 31 | + |
| 32 | +title = "" |
| 33 | +for i in range(2, len(argv)): |
| 34 | + title += argv[i] |
| 35 | + |
| 36 | +timeURL = time.strftime("%Y/%m/%d", time.localtime()) |
| 37 | +solutionURLll = "https://blog.letmefly.xyz/{0}/LeetCode%20{1:04d}.{2}/".format(timeURL, num, quote(title, "utf-8")) |
| 38 | +solutionURLll_humanable = "https://blog.letmefly.xyz/{0}/LeetCode {1:04d}.{2}/".format(timeURL, num, title) |
| 39 | +print(solutionURLll) |
| 40 | + |
| 41 | +# 获取最后一次commit的sha |
| 42 | +def get_latest_commit_sha() -> str: |
| 43 | + try: |
| 44 | + # 执行 git log 命令获取最后一次提交的 SHA |
| 45 | + sha = subprocess.check_output(['git', 'log', '-1', '--pretty=format:%H']).decode('utf-8').strip() |
| 46 | + return sha |
| 47 | + except subprocess.CalledProcessError as e: |
| 48 | + print(f"Error: {e}") |
| 49 | + return None |
| 50 | +lastSHA = get_latest_commit_sha() |
| 51 | + |
| 52 | +# # 认领issue |
| 53 | +# os.system(f'git checkout -b {num}') |
| 54 | +# os.system(f'git push --set-upstream origin {num}') # (#832) |
| 55 | +def getPlatform(): |
| 56 | + platform = sys.platform |
| 57 | + if platform == 'win32': |
| 58 | + return 'Windows' |
| 59 | + elif platform == 'darwin': |
| 60 | + return 'MacOS' |
| 61 | + else: |
| 62 | + return 'Linux(or others)' |
| 63 | +# issueCreateResult = os.popen(f'gh issue create -t "Who can add 1 more problem of LeetCode {num}" -b "By [newSolution.py](https://github.com/LetMeFly666/LeetCode/blob/{lastSHA}/newSolution.py) using GH on {getPlatform()} " -l "题解" -a "@me"').read() |
| 64 | +# print(issueCreateResult) |
| 65 | +# issueNum = int(issueCreateResult.split('\n')[0].split('/')[-1]) |
| 66 | + |
| 67 | +# input('代码写完后按回车生成题解模板:') |
| 68 | + |
| 69 | +with open(nameProblem, "r", encoding="utf-8") as f: |
| 70 | + problem = f.read() |
| 71 | + |
| 72 | +def genSolutionPart(num): |
| 73 | + suffix2markdowncode = { |
| 74 | + 'cpp': ('cpp', 'C++'), # markdown、汉语名 |
| 75 | + 'py': ('python', 'Python'), |
| 76 | + 'c': ('c', 'C语言'), |
| 77 | + 'java': ('java', 'Java'), |
| 78 | + 'go': ('go', 'Go') |
| 79 | + } |
| 80 | + today4code = [] |
| 81 | + for file in os.listdir('Codes'): |
| 82 | + first = file.split('-')[0] |
| 83 | + stop = False |
| 84 | + if first == '1683': |
| 85 | + input('first: 1683') |
| 86 | + stop = True |
| 87 | + try: |
| 88 | + first = int(first) |
| 89 | + except: |
| 90 | + pass |
| 91 | + if stop: |
| 92 | + print(first, num, first == num) |
| 93 | + if first != num: |
| 94 | + continue |
| 95 | + print(file) |
| 96 | + with open(os.path.join('Codes', file), 'r', encoding='utf-8') as f: |
| 97 | + data = f.read() |
| 98 | + time_pattern = re.compile(r"(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})") |
| 99 | + match = time_pattern.search(data) |
| 100 | + if not match: |
| 101 | + print('time not found') |
| 102 | + continue |
| 103 | + year, month, day, hour, miunte, second = map(int, match.groups()) |
| 104 | + fileDay = f'{year:04d}-{day:02d}-{month:02d}' |
| 105 | + today = datetime.date.today().strftime('%Y-%d-%m') |
| 106 | + if fileDay != today: |
| 107 | + print(f'{fileDay} != {today}') |
| 108 | + continue |
| 109 | + today4code.append(os.path.join('Codes', file)) |
| 110 | + # TODO: 一题两解的支持 |
| 111 | + result = """ |
| 112 | +## 解题方法:xx |
| 113 | +
|
| 114 | +11111 |
| 115 | +
|
| 116 | ++ 时间复杂度$O(N^2)$ |
| 117 | ++ 空间复杂度$O(N\log N)$ |
| 118 | +
|
| 119 | +### AC代码 |
| 120 | +""" |
| 121 | + for thisFileType in suffix2markdowncode: # 修改题解中的展示顺序为suffix2markdowncode中出现的顺序而不是后缀字典序(复杂度可优化但没必要) |
| 122 | + for file in today4code: |
| 123 | + fileType = os.path.splitext(file)[-1] |
| 124 | + if fileType.startswith('.'): |
| 125 | + fileType = fileType[1:] |
| 126 | + if fileType != thisFileType: |
| 127 | + continue |
| 128 | + markdowncode = suffix2markdowncode[fileType] |
| 129 | + with open(file, 'r', encoding='utf-8') as f: |
| 130 | + data = f.read() |
| 131 | + # data = removePrefix(data, fileType) # TODO: 移除前面注释以及其他头部文件 |
| 132 | + result += f'\n#### {markdowncode[1]}\n\n```{markdowncode[0]}\n{data}\n```\n' |
| 133 | + return result |
| 134 | + |
| 135 | + |
| 136 | +solution = problem + genSolutionPart(num) +""" |
| 137 | +> 同步发文于[CSDN](https://letmefly.blog.csdn.net/article/details/--------------------------)和我的[个人博客](https://blog.letmefly.xyz/),原创不易,转载经作者同意后请附上[原文链接]({0})哦~ |
| 138 | +> |
| 139 | +> 千篇源码题解[已开源](https://github.com/LetMeFly666/LeetCode) |
| 140 | +""".format(solutionURLll) # .format(solutionURLll, solutionURLll_humanable) |
| 141 | + |
| 142 | +def refreshPublistTime(solution: str) -> str: |
| 143 | + splited = solution.split("\n") |
| 144 | + splited[2] = "date: " + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) |
| 145 | + splited.insert(4, 'categories: [题解, LeetCode]') |
| 146 | + return "\n".join(splited) |
| 147 | + |
| 148 | +solution = refreshPublistTime(solution) |
| 149 | +# print(solution) |
| 150 | + |
| 151 | +exit() |
| 152 | + |
| 153 | +solutionName = "Solutions/LeetCode {0:04d}.{1}.md".format(num, title) |
| 154 | +with open(solutionName, "x", encoding="utf-8") as f: |
| 155 | + f.write(solution) |
| 156 | + |
| 157 | +print("请编辑题解: “{0}”,注意不要更改文件前5行".format(solutionName)) |
| 158 | + |
| 159 | +print("请去掉可能的由其他插件自动生成的头部注释信息,并保存你所编辑的题解") |
| 160 | +csdnid = input("请输入CSDN题解文章的id(11022152):") |
| 161 | +solutionURLcs = "https://letmefly.blog.csdn.net/article/details/{0}".format(csdnid) |
| 162 | + |
| 163 | +with open(solutionName, "r", encoding="utf-8") as f: |
| 164 | + solution = f.read() |
| 165 | + |
| 166 | +solution = solution.replace("--------------------------", csdnid) |
| 167 | +# solution = solution.replace("--------------------------", csdnid) |
| 168 | + |
| 169 | +with open(solutionName, "w", encoding="utf-8") as f: |
| 170 | + f.write(solution) |
| 171 | + |
| 172 | +print("请重新复制所有的题解内容,并粘贴到CSDN中发布") |
| 173 | +print("请在LeetCode中新建、编辑并发布题解") |
| 174 | + |
| 175 | +solutionURLlc = input("LeetCode题解的url: ") |
| 176 | + |
| 177 | +with open("README.md", "r", encoding="utf-8") as f: |
| 178 | + readme = f.read() |
| 179 | + |
| 180 | +def readmeNewLine(readme: str) -> str: |
| 181 | + splited = readme.split("\n") |
| 182 | + def getTiJieBeginEnd(splited: list) -> tuple: |
| 183 | + begin, end = 0, 0 |
| 184 | + for i in range(len(splited)): |
| 185 | + if splited[i] == "|题目名称|困难程度|题目地址|题解地址|CSDN题解|LeetCode题解|": |
| 186 | + begin = i + 2 |
| 187 | + lineSplited = splited[i].split("|") |
| 188 | + if begin and i >= begin: |
| 189 | + if not ord('0') <= ord(lineSplited[1][0]) <= ord('9'): |
| 190 | + end = i |
| 191 | + break |
| 192 | + return (begin, end) |
| 193 | + beginLine, endLine = getTiJieBeginEnd(splited) |
| 194 | + print(beginLine, endLine) |
| 195 | + haveBigger = False |
| 196 | + for i in range(beginLine, endLine): |
| 197 | + thisNum = int(splited[i].split("|")[1].split(".")[0]) |
| 198 | + if thisNum > num: |
| 199 | + haveBigger = True |
| 200 | + break |
| 201 | + if not haveBigger: |
| 202 | + i = endLine |
| 203 | + def generateNewLine(): |
| 204 | + def getHard(): |
| 205 | + solutionSplited = solution.split("\n") |
| 206 | + tags = solutionSplited[3] |
| 207 | + realTags = tags.split("tags: [")[1][:-1].split(", ") |
| 208 | + if "简单" in realTags: |
| 209 | + return "简单" |
| 210 | + elif "中等" in realTags: |
| 211 | + return "中等" |
| 212 | + elif "困难" in realTags: |
| 213 | + return "困难" |
| 214 | + else: |
| 215 | + return input("自动获取难易程度失败!请手动输入[简单/中等/困难]并检查代码: ") |
| 216 | + def getProblemUrl(): |
| 217 | + tempUrl = solutionURLlc |
| 218 | + if tempUrl[len(tempUrl) - 1] != '/': |
| 219 | + tempUrl += "/" |
| 220 | + splitedUrl = tempUrl.split("/") |
| 221 | + del splitedUrl[len(splitedUrl) - 2] |
| 222 | + del splitedUrl[len(splitedUrl) - 2] |
| 223 | + del splitedUrl[len(splitedUrl) - 2] # solutions |
| 224 | + return "/".join(splitedUrl) |
| 225 | + return """|{0:04d}.{1}|{2}|<a href="{3}" target="_blank">题目地址</a>|<a href="{4}" target="_blank">题解地址</a>|<a href="https://letmefly.blog.csdn.net/article/details/{5}" target="_blank">CSDN题解</a>|<a href="{6}" target="_blank">LeetCode题解</a>|""".format(num, title, getHard(), getProblemUrl(), solutionURLll, csdnid, solutionURLlc) |
| 226 | + splited.insert(i, generateNewLine()) |
| 227 | + return "\n".join(splited) |
| 228 | + |
| 229 | +readme = readmeNewLine(readme) |
| 230 | +print(readme) |
| 231 | +with open("README.md", "w", encoding="utf-8") as f: |
| 232 | + f.write(readme) |
| 233 | + |
| 234 | +# commit push pr merge delete-branch |
| 235 | +os.system('git add .') |
| 236 | +def getPrOrIssueMaxNum(prOrIssue: str) -> int: # (#811) |
| 237 | + print(f'max {prOrIssue} number:', end=' ') |
| 238 | + sys.stdout.flush() |
| 239 | + cmd = ['gh', prOrIssue, 'list', '--state', 'all', '--limit', '1', '--json', 'number'] |
| 240 | + result = subprocess.run(cmd, capture_output=True, text=True, check=True) |
| 241 | + data = json.loads(result.stdout) |
| 242 | + print(data) |
| 243 | + return data[0]['number'] |
| 244 | +lastNum = max(getPrOrIssueMaxNum('pr'), getPrOrIssueMaxNum('issue')) |
| 245 | +print(lastNum) |
| 246 | +commitMsg = f'update: 添加问题“{num}.{title}”的代码和题解(#{lastNum + 1})' |
| 247 | +if os.path.exists('.commitmsg') and os.path.isfile('.commitmsg'): # (#795) |
| 248 | + with open('.commitmsg', 'r', encoding='utf-8') as f: |
| 249 | + commitMsgFromfile = f.read() |
| 250 | + if not commitMsgFromfile.startswith('\n'): |
| 251 | + commitMsgFromfile = '\n' + commitMsgFromfile |
| 252 | + commitMsg += commitMsgFromfile |
| 253 | +subprocess.run(['git', 'commit', '-s', '-m', commitMsg]) # os.system('git commit -s -m "{msg}"')的话没法评论多行 |
| 254 | +os.system(f'git push --set-upstream origin {num}') |
| 255 | +cmd = f'gh pr create -t "添加问题“{num}.{title}”的代码和题解" -b "By [newSolution.py](https://github.com/LetMeFly666/LeetCode/blob/{lastSHA}/newSolution.py) using GH on {getPlatform()} | close: #{issueNum}" -l "题解" -a "@me"' |
| 256 | +prResult = os.popen(cmd).read() |
| 257 | +print(prResult) |
| 258 | +prNumber = int(prResult.split('/')[-1]) |
| 259 | +os.system('gh pr edit --add-label "under merge"') |
| 260 | +input('enter when ready to merge:') # 万一给带密码的东西merge了就无法恢复了(虽然这个仓库一次都没有过) |
| 261 | +os.system('gh pr edit --remove-label "under merge"') |
| 262 | +# os.system(f'gh pr merge {prNumber} -r -d') # rebase没有verified的标,且sha也不一样 |
| 263 | +def get_commit_diff(): |
| 264 | + # 获取题解分支比master多出的提交次数 |
| 265 | + try: |
| 266 | + count = int(subprocess.check_output( |
| 267 | + ['git', 'rev-list', '--count', f'master..{num}'], |
| 268 | + stderr=subprocess.DEVNULL # 屏蔽错误输出 |
| 269 | + ).decode().strip()) |
| 270 | + except subprocess.CalledProcessError: |
| 271 | + print("无法获取提交差异,请确认分支存在") |
| 272 | + return 1 |
| 273 | + return count |
| 274 | +commitCount = get_commit_diff() |
| 275 | +if commitCount < 2: # 直接本地merge,即不是rebase又减少一次merge记录 | 这个merge大概不会产生冲突 |
| 276 | + os.system(f'git switch master') |
| 277 | + os.system(f'git merge {num}') |
| 278 | + os.system(f'git push') |
| 279 | + os.system(f'git branch -d {num}') |
| 280 | + os.system(f'git push --delete origin {num}') |
| 281 | +else: # 使用gh在github上通过squash的方式merge | 在本地squash merge并push的话github无法自动识别并关闭pr |
| 282 | + os.system(f'gh pr merge -s -d -t "update: 添加问题“{num}.{title}”的代码和题解(#{prNumber})"') |
| 283 | +# https://github.com/LetMeFly666/LeetCode/blob/3435204860a8a85aa666618d90f40916dc70a1f1/reassign.py |
| 284 | +def syncGitcodeCSDN(): |
| 285 | + nowCWD = os.getcwd() |
| 286 | + os.chdir('OtherSource/gitcode_knowledge') |
| 287 | + os.system('git pull --force') |
| 288 | + # 判断一个commit是否按配置签名 |
| 289 | + def verify_commit(commit_hash: str) -> bool: |
| 290 | + result = subprocess.run( |
| 291 | + ['git', 'verify-commit', commit_hash], |
| 292 | + stdout=subprocess.PIPE, |
| 293 | + stderr=subprocess.PIPE, |
| 294 | + ) |
| 295 | + return not result.returncode |
| 296 | + # 获取一个commit的上一个commit |
| 297 | + def get_parent_commit(commit_hash: str) -> str: |
| 298 | + result = subprocess.run( |
| 299 | + ['git', 'log', commit_hash, '--pretty=%H', '-n', '2'], |
| 300 | + stdout=subprocess.PIPE, |
| 301 | + stderr=subprocess.PIPE, |
| 302 | + ) |
| 303 | + return result.stdout.decode('utf-8').strip().split()[-1] |
| 304 | + # 将从某次commit开始至HEAD的所有commit重新签名 |
| 305 | + def re_assign(commit_hash: str) -> None: |
| 306 | + # cmd = f'git filter-branch -f --commit-filter \'git commit-tree -S "$@";\' {commit_hash}..HEAD' |
| 307 | + # print(cmd) |
| 308 | + # os.system(cmd) |
| 309 | + env = os.environ.copy() |
| 310 | + env['FILTER_BRANCH_SQUELCH_WARNING'] = "1" |
| 311 | + result = subprocess.run( |
| 312 | + ["git", "filter-branch", "-f", "--commit-filter", 'git commit-tree -S "$@";', f"{commit_hash}..HEAD"], |
| 313 | + env=env, |
| 314 | + ) |
| 315 | + print(result.returncode) |
| 316 | + # subprocess.Popen(cmd) |
| 317 | + def re_assign_main(): |
| 318 | + # re_assign('HEAD~2') |
| 319 | + # return |
| 320 | + if verify_commit('HEAD'): # HEAD的签名也能被验证 |
| 321 | + return |
| 322 | + notVerified = 'HEAD' |
| 323 | + while True: |
| 324 | + next = get_parent_commit(notVerified) |
| 325 | + if next == notVerified: |
| 326 | + break |
| 327 | + notVerified = next |
| 328 | + if verify_commit(next): |
| 329 | + break |
| 330 | + print(notVerified) |
| 331 | + re_assign(notVerified) |
| 332 | + re_assign_main() |
| 333 | + os.system('git push --force') # resign gitcode |
| 334 | + os.system('git push Let main:From_GitCode_CSDN') |
| 335 | + os.chdir(nowCWD) |
| 336 | +syncGitcodeCSDN() |
0 commit comments