Skip to content

Commit 5632403

Browse files
committed
feat(cli-command): add command leetcode for generating new solution post
1 parent 8e75763 commit 5632403

File tree

7 files changed

+75104
-0
lines changed

7 files changed

+75104
-0
lines changed

Diff for: .taskfiles/task_leetcode.yaml

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
version: "3"
2+
3+
tasks:
4+
new:
5+
desc: Generate new post for LeetCode solution
6+
cmds:
7+
- pnpm lc:new {{.CLI_ARGS}}
8+
silent: true
9+
10+
commit:
11+
desc: Commit code for a specific LeetCode problem
12+
cmds:
13+
- |
14+
git commit -m "chore(solution): add solution for LeetCode problem No.{{.CLI_ARGS}}"
15+
silent: true

Diff for: Taskfile.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ includes:
55
git: ./.taskfiles/task_git.yaml
66
release: ./.taskfiles/task_release.yaml
77
dep: ./.taskfiles/task_dep.yaml
8+
lc: ./.taskfiles/task_leetcode.yaml

Diff for: package.json

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"version": "2.0.1",
66
"license": "MIT",
77
"scripts": {
8+
"lc:new": "tsx ./src/cmd/leetcode.ts",
89
"dev": "astro dev",
910
"start": "astro dev",
1011
"build": "astro build",

Diff for: src/cmd/leetcode.ts

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import { Buffer } from 'buffer'
2+
import { Command, CommanderError, InvalidArgumentError } from 'commander'
3+
import dayjs from 'dayjs'
4+
import fs from 'fs'
5+
import Mustache from 'mustache'
6+
import path from 'path'
7+
import { fileURLToPath } from 'url'
8+
9+
import siteConfig from '@/configs/site'
10+
import lc from '@/leetcode/data/problem_set.json'
11+
import type { Difficulty, LeetCodePostFrontMatter, LeetCodeQuestion } from '@/leetcode/types'
12+
13+
const ERR_QUESTION_DOES_NOT_EXIST_OR_NOT_SUPPORTED = 1
14+
const ERR_QUESTION_WAS_NOT_FOUND = 2
15+
const ERR_READ_FROM_FILE_FAILED = 3
16+
const ERR_WRITE_TO_FILE_FAILED = 4
17+
18+
const __filename = fileURLToPath(import.meta.url)
19+
const __dirname = path.dirname(__filename)
20+
const program = new Command()
21+
22+
const templateBaseDir = path.join(__dirname, '../leetcode/templates')
23+
const templateExtension = 'mustache'
24+
const questionList = lc.data.problemsetQuestionList
25+
26+
const getQuestionNumber: () => number = () => {
27+
program.parse()
28+
29+
const question: string = program.args[0] || ''
30+
const regex = new RegExp('\\d+')
31+
32+
if (!regex.test(question)) {
33+
throw new InvalidArgumentError(`Input must be number: ${program.args[0]}`)
34+
}
35+
36+
const questionNumber = Number(question)
37+
38+
if (questionNumber < 0) {
39+
throw new InvalidArgumentError(`Input must be a positive integer: ${program.args[0]}`)
40+
}
41+
42+
return questionNumber
43+
}
44+
45+
const getQuestion: (questionNumber: number) => LeetCodeQuestion | undefined = (questionNumber: number) => {
46+
const totalQuestions = questionList.total
47+
48+
if (questionNumber > totalQuestions) {
49+
throw new CommanderError(
50+
ERR_QUESTION_DOES_NOT_EXIST_OR_NOT_SUPPORTED,
51+
ERR_QUESTION_DOES_NOT_EXIST_OR_NOT_SUPPORTED.toString(),
52+
`Question #${questionNumber} is not exist or not supported`
53+
)
54+
}
55+
56+
const questions = questionList.questions
57+
const filteredQuestions = questions.filter(q => q.frontendQuestionId === questionNumber.toString())
58+
59+
if (filteredQuestions.length === 0) {
60+
throw new CommanderError(
61+
ERR_QUESTION_WAS_NOT_FOUND,
62+
ERR_QUESTION_WAS_NOT_FOUND.toString(),
63+
`Question #${questionNumber} was not found`
64+
)
65+
}
66+
67+
return filteredQuestions[0]
68+
}
69+
70+
const readFromFile: (filePath: string) => string = (filePath: string) => {
71+
try {
72+
return fs.readFileSync(filePath, 'utf8')
73+
} catch (err) {
74+
throw new CommanderError(
75+
ERR_READ_FROM_FILE_FAILED,
76+
ERR_READ_FROM_FILE_FAILED.toString(),
77+
`Failed to read from ${filePath}`
78+
)
79+
}
80+
}
81+
82+
const writeToFile: (filePath: string, content: string) => void = (filePath: string, content: string) => {
83+
try {
84+
const data = Buffer.from(content)
85+
fs.writeFileSync(filePath, data, { flag: 'ax' })
86+
console.info(`Wrote to file ${filePath} successfully!`)
87+
} catch (e) {
88+
throw new CommanderError(
89+
ERR_WRITE_TO_FILE_FAILED,
90+
ERR_WRITE_TO_FILE_FAILED.toString(),
91+
`Failed to write to ${filePath}`
92+
)
93+
}
94+
}
95+
96+
const render: (fileName: string, templateData: object) => string = (fileName: string, templateData: object) => {
97+
const content = readFromFile(path.join(templateBaseDir, `./${fileName}.${templateExtension}`))
98+
return Mustache.render(content, templateData)
99+
}
100+
101+
// Main program
102+
const generateNewPost: () => void = async () => {
103+
const questionNumber: number = getQuestionNumber()
104+
const question = getQuestion(questionNumber)
105+
106+
if (question === undefined) {
107+
process.exit(1)
108+
}
109+
110+
const title = `${question.frontendQuestionId}. ${question.title}`
111+
const slug = `${question.frontendQuestionId.padStart(4, '0')}-${question.titleSlug}`
112+
const keywords: string[] = question.titleSlug.split('-')
113+
const author: string = siteConfig.author.nickname
114+
const tags: string[] = question.topicTags.map(tag => tag.name)
115+
const pubDate: string = dayjs().format()
116+
const difficulty: Difficulty = question.difficulty as Difficulty
117+
118+
const templateData: LeetCodePostFrontMatter = { title, slug, keywords, author, pubDate, difficulty, tags }
119+
120+
const output = await render('leetcode.md', templateData)
121+
122+
writeToFile(path.join(__dirname, '../content/leetcode-solutions', `${slug}.md`), output)
123+
}
124+
125+
generateNewPost()

0 commit comments

Comments
 (0)