290 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			V
		
	
	
			
		
		
	
	
			290 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			V
		
	
	
| module main
 | |
| 
 | |
| import os
 | |
| import rand
 | |
| import term
 | |
| import v.pref
 | |
| 
 | |
| const (
 | |
| 	too_long_line_length = 100
 | |
| 	term_colors          = term.can_show_color_on_stderr()
 | |
| )
 | |
| 
 | |
| fn main() {
 | |
| 	files_paths := if '-all' in os.args { md_file_paths() } else { os.args[1..] }
 | |
| 	mut warnings := 0
 | |
| 	mut errors := 0
 | |
| 	mut oks := 0
 | |
| 	mut all_md_files := []MDFile{}
 | |
| 	for file_path in files_paths {
 | |
| 		real_path := os.real_path(file_path)
 | |
| 		lines := os.read_lines(real_path) or {
 | |
| 			println('"$file_path" does not exist')
 | |
| 			warnings++
 | |
| 			continue
 | |
| 		}
 | |
| 		mut mdfile := MDFile{
 | |
| 			path: file_path
 | |
| 		}
 | |
| 		for i, line in lines {
 | |
| 			if line.len > too_long_line_length {
 | |
| 				if mdfile.state == .vexample {
 | |
| 					println(wline(file_path, i, line.len, 'long V example line'))
 | |
| 					warnings++
 | |
| 				} else if mdfile.state == .codeblock {
 | |
| 					println(wline(file_path, i, line.len, 'long code block line'))
 | |
| 					warnings++
 | |
| 				} else if line.starts_with('|') {
 | |
| 					println(wline(file_path, i, line.len, 'long table'))
 | |
| 					warnings++
 | |
| 				} else if line.contains('https') {
 | |
| 					println(wline(file_path, i, line.len, 'long link'))
 | |
| 					warnings++
 | |
| 				} else {
 | |
| 					eprintln(eline(file_path, i, line.len, 'line too long'))
 | |
| 					errors++
 | |
| 				}
 | |
| 			}
 | |
| 			mdfile.parse_line(i, line)
 | |
| 		}
 | |
| 		all_md_files << mdfile
 | |
| 	}
 | |
| 	for mut mdfile in all_md_files {
 | |
| 		new_errors, new_oks := mdfile.check_examples()
 | |
| 		errors += new_errors
 | |
| 		oks += new_oks
 | |
| 	}
 | |
| 	// println('all_md_files: $all_md_files')
 | |
| 	if warnings > 0 || errors > 0 || oks > 0 {
 | |
| 		println('\nWarnings: $warnings | Errors: $errors | OKs: $oks')
 | |
| 	}
 | |
| 	if errors > 0 {
 | |
| 		exit(1)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| fn md_file_paths() []string {
 | |
| 	mut files_to_check := []string{}
 | |
| 	md_files := os.walk_ext('.', '.md')
 | |
| 	for file in md_files {
 | |
| 		if file.starts_with('./thirdparty') {
 | |
| 			continue
 | |
| 		}
 | |
| 		if file.contains('CHANGELOG') {
 | |
| 			continue
 | |
| 		}
 | |
| 		files_to_check << file
 | |
| 	}
 | |
| 	return files_to_check
 | |
| }
 | |
| 
 | |
| fn ftext(s string, cb fn (string) string) string {
 | |
| 	if term_colors {
 | |
| 		return cb(s)
 | |
| 	}
 | |
| 	return s
 | |
| }
 | |
| 
 | |
| fn btext(s string) string {
 | |
| 	return ftext(s, term.bold)
 | |
| }
 | |
| 
 | |
| fn mtext(s string) string {
 | |
| 	return ftext(s, term.magenta)
 | |
| }
 | |
| 
 | |
| fn rtext(s string) string {
 | |
| 	return ftext(s, term.red)
 | |
| }
 | |
| 
 | |
| fn wline(file_path string, lnumber int, column int, message string) string {
 | |
| 	return btext('$file_path:${lnumber + 1}:${column + 1}:') + btext(mtext(' warn:')) + rtext(' $message')
 | |
| }
 | |
| 
 | |
| fn eline(file_path string, lnumber int, column int, message string) string {
 | |
| 	return btext('$file_path:${lnumber + 1}:${column + 1}:') + btext(rtext(' error: $message'))
 | |
| }
 | |
| 
 | |
| const (
 | |
| 	default_command = 'compile'
 | |
| )
 | |
| 
 | |
| struct VCodeExample {
 | |
| mut:
 | |
| 	text    []string
 | |
| 	command string
 | |
| 	sline   int
 | |
| 	eline   int
 | |
| }
 | |
| 
 | |
| enum MDFileParserState {
 | |
| 	markdown
 | |
| 	vexample
 | |
| 	codeblock
 | |
| }
 | |
| 
 | |
| struct MDFile {
 | |
| 	path     string
 | |
| mut:
 | |
| 	examples []VCodeExample
 | |
| 	current  VCodeExample
 | |
| 	state    MDFileParserState = .markdown
 | |
| }
 | |
| 
 | |
| fn (mut f MDFile) parse_line(lnumber int, line string) {
 | |
| 	if line.starts_with('```v') {
 | |
| 		if f.state == .markdown {
 | |
| 			f.state = .vexample
 | |
| 			mut command := line.replace('```v', '').trim_space()
 | |
| 			if command == '' {
 | |
| 				command = default_command
 | |
| 			}
 | |
| 			f.current = VCodeExample{
 | |
| 				sline: lnumber
 | |
| 				command: command
 | |
| 			}
 | |
| 		}
 | |
| 		return
 | |
| 	}
 | |
| 	if line.starts_with('```') {
 | |
| 		match f.state {
 | |
| 			.vexample {
 | |
| 				f.state = .markdown
 | |
| 				f.current.eline = lnumber
 | |
| 				f.examples << f.current
 | |
| 				f.current = VCodeExample{}
 | |
| 				return
 | |
| 			}
 | |
| 			.codeblock {
 | |
| 				f.state = .markdown
 | |
| 				return
 | |
| 			}
 | |
| 			.markdown {
 | |
| 				f.state = .codeblock
 | |
| 				return
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	if f.state == .vexample {
 | |
| 		f.current.text << line
 | |
| 	}
 | |
| }
 | |
| 
 | |
| fn (mut f MDFile) dump() {
 | |
| 	for e in f.examples {
 | |
| 		eprintln('f.path: $f.path | example: $e')
 | |
| 	}
 | |
| }
 | |
| 
 | |
| fn (mut f MDFile) check_examples() (int, int) {
 | |
| 	mut errors := 0
 | |
| 	mut oks := 0
 | |
| 	vexe := pref.vexe_path()
 | |
| 	for e in f.examples {
 | |
| 		if e.command == 'ignore' {
 | |
| 			continue
 | |
| 		}
 | |
| 		if e.command == 'wip' {
 | |
| 			continue
 | |
| 		}
 | |
| 		fname := os.base(f.path).replace('.md', '_md')
 | |
| 		uid := rand.ulid()
 | |
| 		vfile := os.join_path(os.temp_dir(), 'check_${fname}_example_${e.sline}__${e.eline}__${uid}.v')
 | |
| 		mut should_cleanup_vfile := true
 | |
| 		// eprintln('>>> checking example $vfile ...')
 | |
| 		vcontent := e.text.join('\n') + '\n'
 | |
| 		os.write_file(vfile, vcontent) or { panic(err) }
 | |
| 		mut acommands := e.command.split(' ')
 | |
| 		nofmt := 'nofmt' in acommands
 | |
| 		for command in acommands {
 | |
| 			match command {
 | |
| 				'compile' {
 | |
| 					res := os.system('"$vexe" -w -Wfatal-errors -o x.c $vfile')
 | |
| 					os.rm('x.c') or { }
 | |
| 					fmt_res := if nofmt { 0 } else { os.system('"$vexe" fmt -verify $vfile') }
 | |
| 					if res != 0 || fmt_res != 0 {
 | |
| 						if res != 0 {
 | |
| 							eprintln(eline(f.path, e.sline, 0, 'example failed to compile'))
 | |
| 						}
 | |
| 						if fmt_res != 0 {
 | |
| 							eprintln(eline(f.path, e.sline, 0, 'example is not formatted'))
 | |
| 						}
 | |
| 						eprintln(vcontent)
 | |
| 						should_cleanup_vfile = false
 | |
| 						errors++
 | |
| 						continue
 | |
| 					}
 | |
| 					oks++
 | |
| 				}
 | |
| 				'live' {
 | |
| 					res := os.system('"$vexe" -w -Wfatal-errors -live -o x.c $vfile')
 | |
| 					fmt_res := if nofmt { 0 } else { os.system('"$vexe" fmt -verify $vfile') }
 | |
| 					if res != 0 || fmt_res != 0 {
 | |
| 						if res != 0 {
 | |
| 							eprintln(eline(f.path, e.sline, 0, 'example failed to compile with -live'))
 | |
| 						}
 | |
| 						if fmt_res != 0 {
 | |
| 							eprintln(eline(f.path, e.sline, 0, 'example is not formatted'))
 | |
| 						}
 | |
| 						eprintln(vcontent)
 | |
| 						should_cleanup_vfile = false
 | |
| 						errors++
 | |
| 						continue
 | |
| 					}
 | |
| 					oks++
 | |
| 				}
 | |
| 				'failcompile' {
 | |
| 					res := os.system('"$vexe" -w -Wfatal-errors -o x.c $vfile')
 | |
| 					os.rm('x.c') or { }
 | |
| 					if res == 0 {
 | |
| 						eprintln(eline(f.path, e.sline, 0, '`failcompile` example compiled'))
 | |
| 						eprintln(vcontent)
 | |
| 						should_cleanup_vfile = false
 | |
| 						errors++
 | |
| 						continue
 | |
| 					}
 | |
| 					oks++
 | |
| 				}
 | |
| 				'oksyntax' {
 | |
| 					res := os.system('"$vexe" -w -Wfatal-errors -check-syntax $vfile')
 | |
| 					fmt_res := if nofmt { 0 } else { os.system('"$vexe" fmt -verify $vfile') }
 | |
| 					if res != 0 || fmt_res != 0 {
 | |
| 						if res != 0 {
 | |
| 							eprintln(eline(f.path, e.sline, 0, '`oksyntax` example with invalid syntax'))
 | |
| 						}
 | |
| 						if fmt_res != 0 {
 | |
| 							eprintln(eline(f.path, e.sline, 0, '`oksyntax` example is not formatted'))
 | |
| 						}
 | |
| 						eprintln(vcontent)
 | |
| 						should_cleanup_vfile = false
 | |
| 						errors++
 | |
| 						continue
 | |
| 					}
 | |
| 					oks++
 | |
| 				}
 | |
| 				'badsyntax' {
 | |
| 					res := os.system('"$vexe" -w -Wfatal-errors -check-syntax $vfile')
 | |
| 					if res == 0 {
 | |
| 						eprintln(eline(f.path, e.sline, 0, '`badsyntax` example can be parsed fine'))
 | |
| 						eprintln(vcontent)
 | |
| 						should_cleanup_vfile = false
 | |
| 						errors++
 | |
| 						continue
 | |
| 					}
 | |
| 					oks++
 | |
| 				}
 | |
| 				'nofmt' {}
 | |
| 				else {
 | |
| 					eprintln(eline(f.path, e.sline, 0, 'unrecognized command: "$command", use one of: wip/ignore/compile/failcompile/oksyntax/badsyntax'))
 | |
| 					should_cleanup_vfile = false
 | |
| 					errors++
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		if should_cleanup_vfile {
 | |
| 			os.rm(vfile) or { panic(err) }
 | |
| 		}
 | |
| 	}
 | |
| 	return errors, oks
 | |
| }
 |