tools: instantly generate the entire changelog in changelog_helper.v

This commit is contained in:
Alexander Medvednikov 2024-03-20 06:51:55 +03:00
parent 44c78ed761
commit cf7d6cd648
2 changed files with 185 additions and 61 deletions

View file

@ -1,3 +1,5 @@
## V 0.4.5 TODO
## V 0.4.4 ## V 0.4.4
*9 January 2024* *9 January 2024*

View file

@ -15,30 +15,47 @@ enum Category {
db db
native native
cgen cgen
js_backend
comptime comptime
tools tools
compiler_internals compiler_internals
examples examples
vfmt vfmt
os_support
} }
/* const category_titles = '#### Improvements in the language
#### Improvements in the language
#### Breaking changes #### Breaking changes
#### Checker improvements/fixes #### Checker improvements/fixes
#### Parser improvements #### Parser improvements
#### Compiler internals #### Compiler internals
#### Standard library #### Standard library
#### Web #### Web
#### ORM #### ORM
#### Database drivers #### Database drivers
#### Native backend #### Native backend
#### C backend #### C backend
#### JavaScript backend
#### vfmt #### vfmt
#### Tools #### Tools
#### Operating System support #### Operating System support
#### Examples #### Examples
*/ '
struct Line { struct Line {
category Category category Category
@ -48,22 +65,63 @@ struct Line {
const log_txt = 'log.txt' const log_txt = 'log.txt'
struct App { struct App {
version string // e.g. "0.4.5"
total_lines int total_lines int
mut: mut:
result string // resulting CHANGELOG.md
counter int counter int
} }
const is_interactive = false
// Instantly updates CHANGELOG.md without confirming each line
fn no_interactive(version string) {
}
fn main() { fn main() {
if !os.exists(log_txt) { mut version := ''
os.execute(git_log_cmd + ' > ' + log_txt)
println('log.txt generated, remove unnecessary commits from it and run the tool again') if os.args.len == 2 && os.args[1].starts_with('0.') {
version = os.args[1]
// no_interactive(version)
// return
} else {
println('Usage: v run tools/changelog_helper.v 0.4.5')
return return
} }
lines := os.read_lines(log_txt)! if !os.exists(log_txt) {
changelog_txt := os.read_file('CHANGELOG.md')!.to_lower() os.execute(git_log_cmd + ' > ' + log_txt)
println('log.txt generated')
// println('log.txt generated, remove unnecessary commits from it and run the tool again')
// return
}
mut lines := os.read_lines(log_txt)!
// Trim everything before current version, commit "(tag: 0.4.4) V 0.4.4"
mut prev_version := (version.replace('.', '').int() - 1).str()
prev_version = '0.${prev_version[0].ascii_str()}.${prev_version[1].ascii_str()}'
println('prev version=${prev_version}')
for i, line in lines {
if line == ('V ${prev_version}') {
lines = lines[..i].clone()
break
}
}
os.write_file(log_txt, lines.join('\n'))!
if true {
// return
}
mut app := &App{ mut app := &App{
total_lines: lines.len total_lines: lines.len
} }
// Write categories at the top first
app.result = os.read_file('CHANGELOG.md')!.replace_once('V ${version} TODO', 'V ${version}\n' +
category_titles)
os.write_file('CHANGELOG.md', app.result)!
changelog_txt := os.read_file('CHANGELOG.md')!.to_lower()
if true {
// println(changelog_txt)
// return
}
// mut counter := 0 // to display how many commits are left // mut counter := 0 // to display how many commits are left
for line in lines { for line in lines {
s := line.trim_space() s := line.trim_space()
@ -81,7 +139,11 @@ fn main() {
continue continue
} }
app.process_line(line)! app.process_line(line.trim_space())!
}
println('writing changelog.md')
if !is_interactive {
os.write_file('CHANGELOG.md', app.result)!
} }
println('done.') println('done.')
} }
@ -96,11 +158,19 @@ fn (mut app App) process_line(text string) ! {
} }
prefix := text[..semicolon_pos] prefix := text[..semicolon_pos]
// Get category based on keywords in the commit message/prefix // Get category based on keywords in the commit message/prefix
mut category := Category.improvements mut category := Category.examples
if text.contains('checker:') { if text.contains('checker:') {
category = .checker category = .checker
} else if text.contains('cgen:') { } else if is_examples(text) {
// Always skip examples and typos fixes
category = .examples
return
} else if is_os_support(text) {
category = .os_support
} else if is_cgen(text) {
category = .cgen category = .cgen
} else if is_js_backend(text) {
category = .js_backend
} else if is_db(text) { } else if is_db(text) {
category = .db category = .db
} else if is_stdlib(text) { } else if is_stdlib(text) {
@ -121,9 +191,6 @@ fn (mut app App) process_line(text string) ! {
category = .native category = .native
} else if is_vfmt(text) { } else if is_vfmt(text) {
category = .vfmt category = .vfmt
} else if is_examples(text) {
// TODO maybe always skip these as well?
category = .examples
} else if text.contains('docs:') || text.contains('doc:') { } else if text.contains('docs:') || text.contains('doc:') {
// Always skip docs // Always skip docs
delete_processed_line_from_log(text)! delete_processed_line_from_log(text)!
@ -133,6 +200,7 @@ fn (mut app App) process_line(text string) ! {
else { else {
return return
} }
println('process_line: cat=${category} "${text}"')
// Trim everything to the left of `:` for some commits (e.g. `checker: `) // Trim everything to the left of `:` for some commits (e.g. `checker: `)
mut s := text mut s := text
@ -140,10 +208,13 @@ fn (mut app App) process_line(text string) ! {
// if true { // if true {
// exit(0) // exit(0)
//} //}
if semicolon_pos < 15 && prefix in ['checker', 'cgen'] { if (semicolon_pos < 15
&& prefix in ['checker', 'cgen', 'parser', 'v.parser', 'ast', 'jsgen', 'v.gen.js', 'fmt', 'vfmt'])
|| (semicolon_pos < 30 && prefix.contains(', ')) {
s = '- ' + text[semicolon_pos + 2..].capitalize() s = '- ' + text[semicolon_pos + 2..].capitalize()
} }
if is_interactive {
// Get input from the user // Get input from the user
print('\033[H\033[J') print('\033[H\033[J')
println('${app.counter} / ${app.total_lines}') println('${app.counter} / ${app.total_lines}')
@ -155,7 +226,7 @@ fn (mut app App) process_line(text string) ! {
'' { '' {
println('GOT ENTER') println('GOT ENTER')
line := Line{category, s} line := Line{category, s}
save_line(line)! save_line_interactive(line)!
} }
'n', '0', 'no' { 'n', '0', 'no' {
// Ignore commit // Ignore commit
@ -176,7 +247,7 @@ fn (mut app App) process_line(text string) ! {
} else { } else {
unsafe { unsafe {
line := Line{Category(custom_category - 1), s} line := Line{Category(custom_category - 1), s}
save_line(line)! save_line_interactive(line)!
} }
break break
} }
@ -185,21 +256,23 @@ fn (mut app App) process_line(text string) ! {
else {} else {}
} }
app.counter++ app.counter++
} else {
line := Line{category, s}
app.save_line(line)!
}
// Don't forget to remove the line we just processed from log.txt // Don't forget to remove the line we just processed from log.txt
delete_processed_line_from_log(text)! delete_processed_line_from_log(text)!
} }
fn save_line(line Line) ! { fn (mut app App) save_line(line Line) ! {
println('save line ${line}') // println('save line ${line}')
mut txt := os.read_file('CHANGELOG.md')! app.result = line.write_at_category(app.result) or { return error('') }
}
// match line.category { fn save_line_interactive(line Line) ! {
//.checker { println('save line interactive ${line}')
mut txt := os.read_file('CHANGELOG.md')!
txt = line.write_at_category(txt) or { return error('') } txt = line.write_at_category(txt) or { return error('') }
// println(txt.limit(1000))
//}
// else {}
//}
os.write_file('CHANGELOG.md', txt)! os.write_file('CHANGELOG.md', txt)!
} }
@ -214,11 +287,13 @@ const category_map = {
.db: '#### Database drivers' .db: '#### Database drivers'
.native: '#### Native backend' .native: '#### Native backend'
.cgen: '#### C backend' .cgen: '#### C backend'
.js_backend: '#### JavaScript backend'
.comptime: '#### Comptime' .comptime: '#### Comptime'
.tools: '#### Tools' .tools: '#### Tools'
.compiler_internals: '#### Compiler internals' .compiler_internals: '#### Compiler internals'
.examples: '#### Examples' .examples: '#### Examples'
.vfmt: '#### vfmt' .vfmt: '#### vfmt'
.os_support: '#### Operating System'
} }
fn (l Line) write_at_category(txt string) ?string { fn (l Line) write_at_category(txt string) ?string {
@ -276,6 +351,7 @@ const db_strings = [
const improvements_strings = [ const improvements_strings = [
'all:', 'all:',
'v:', 'v:',
'coroutines:',
] ]
const parser_strings = [ const parser_strings = [
@ -293,7 +369,7 @@ const stdlib_strings = [
'math:', 'math:',
'math.big', 'math.big',
'crypto', 'crypto',
'sokol:', 'sokol',
'os:', 'os:',
'rand:', 'rand:',
'math:', 'math:',
@ -309,6 +385,10 @@ const stdlib_strings = [
'readline', 'readline',
'cli:', 'cli:',
'eventbus:', 'eventbus:',
'encoding.',
'bitfield:',
'io:',
'log:',
] ]
fn is_stdlib(text string) bool { fn is_stdlib(text string) bool {
@ -327,6 +407,25 @@ fn is_orm(text string) bool {
return is_xxx(text, orm_strings) return is_xxx(text, orm_strings)
} }
const cgen_strings = [
'cgen:',
'v.gen.c:',
]
fn is_cgen(text string) bool {
return is_xxx(text, cgen_strings)
}
const js_backend_strings = [
'js:',
'v.gen.js:',
'jsgen:',
]
fn is_js_backend(text string) bool {
return is_xxx(text, js_backend_strings)
}
const internal_strings = [ const internal_strings = [
'scanner:', 'scanner:',
'transformer:', 'transformer:',
@ -348,6 +447,10 @@ const examples_strings = [
'tests', 'tests',
'readme:', 'readme:',
'.md:', '.md:',
'typos',
' typo',
'cleanup',
'clean up',
] ]
fn is_examples(text string) bool { fn is_examples(text string) bool {
@ -362,6 +465,7 @@ const tools_strings = [
'gitignore', 'gitignore',
'benchmark', 'benchmark',
'v.help:', 'v.help:',
'vtest',
] ]
fn is_tools(text string) bool { fn is_tools(text string) bool {
@ -374,6 +478,7 @@ fn is_parser(text string) bool {
const web_strings = [ const web_strings = [
'vweb:', 'vweb:',
'x.vweb:',
'websocket:', 'websocket:',
'picoev:', 'picoev:',
'mbedtls', 'mbedtls',
@ -404,6 +509,23 @@ fn is_vfmt(text string) bool {
return is_xxx(text, vfmt_strings) return is_xxx(text, vfmt_strings)
} }
const os_support_strings = [
'FreeBSD',
'freebsd',
'OpenBSD',
'openbsd',
'macOS',
'macos',
'Windows',
'windows',
'Linux',
'linux',
]
fn is_os_support(text string) bool {
return is_xxx(text, os_support_strings)
}
fn is_xxx(text string, words []string) bool { fn is_xxx(text string, words []string) bool {
for s in words { for s in words {
if text.contains(s) { if text.contains(s) {