2022-01-04 12:21:08 +03:00
|
|
|
|
// Copyright (c) 2019-2022 Alexander Medvednikov. All rights reserved.
|
2021-02-05 18:46:20 +03:00
|
|
|
|
// Use of this source code is governed by an MIT license
|
|
|
|
|
// that can be found in the LICENSE file.
|
2020-02-20 15:41:03 +03:00
|
|
|
|
module main
|
|
|
|
|
|
|
|
|
|
import os
|
2021-01-10 16:58:45 +03:00
|
|
|
|
import os.cmdline
|
2020-11-03 03:04:14 +03:00
|
|
|
|
import rand
|
|
|
|
|
import term
|
2023-02-01 12:18:23 +03:00
|
|
|
|
import v.help
|
2021-06-14 13:12:02 +03:00
|
|
|
|
import regex
|
2020-02-20 15:41:03 +03:00
|
|
|
|
|
|
|
|
|
const (
|
2021-08-16 10:11:44 +03:00
|
|
|
|
too_long_line_length_example = 120
|
|
|
|
|
too_long_line_length_codeblock = 120
|
|
|
|
|
too_long_line_length_table = 120
|
|
|
|
|
too_long_line_length_link = 150
|
|
|
|
|
too_long_line_length_other = 100
|
|
|
|
|
term_colors = term.can_show_color_on_stderr()
|
|
|
|
|
hide_warnings = '-hide-warnings' in os.args || '-w' in os.args
|
|
|
|
|
show_progress = os.getenv('GITHUB_JOB') == '' && '-silent' !in os.args
|
|
|
|
|
non_option_args = cmdline.only_non_options(os.args[2..])
|
2022-01-31 23:51:04 +03:00
|
|
|
|
is_verbose = os.getenv('VERBOSE') != ''
|
2022-11-15 16:53:13 +03:00
|
|
|
|
vcheckfolder = os.join_path(os.vtmp_dir(), 'v', 'vcheck_${os.getuid()}')
|
2022-10-26 21:47:36 +03:00
|
|
|
|
should_autofix = os.getenv('VAUTOFIX') != ''
|
|
|
|
|
vexe = @VEXE
|
2020-02-20 15:41:03 +03:00
|
|
|
|
)
|
|
|
|
|
|
2021-03-10 18:46:07 +03:00
|
|
|
|
struct CheckResult {
|
|
|
|
|
pub mut:
|
|
|
|
|
warnings int
|
|
|
|
|
errors int
|
|
|
|
|
oks int
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn (v1 CheckResult) + (v2 CheckResult) CheckResult {
|
|
|
|
|
return CheckResult{
|
|
|
|
|
warnings: v1.warnings + v2.warnings
|
|
|
|
|
errors: v1.errors + v2.errors
|
|
|
|
|
oks: v1.oks + v2.oks
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-20 15:41:03 +03:00
|
|
|
|
fn main() {
|
2021-02-05 18:46:20 +03:00
|
|
|
|
if non_option_args.len == 0 || '-help' in os.args {
|
2023-02-01 12:18:23 +03:00
|
|
|
|
help.print_and_exit('check-md')
|
2021-01-10 16:58:45 +03:00
|
|
|
|
exit(0)
|
|
|
|
|
}
|
2021-04-12 14:59:27 +03:00
|
|
|
|
if '-all' in os.args {
|
2021-02-05 18:46:20 +03:00
|
|
|
|
println('´-all´ flag is deprecated. Please use ´v check-md .´ instead.')
|
|
|
|
|
exit(1)
|
|
|
|
|
}
|
2021-04-12 15:51:28 +03:00
|
|
|
|
if show_progress {
|
|
|
|
|
// this is intended to be replaced by the progress lines
|
|
|
|
|
println('')
|
|
|
|
|
}
|
2021-02-05 18:46:20 +03:00
|
|
|
|
mut files_paths := non_option_args.clone()
|
2021-03-10 18:46:07 +03:00
|
|
|
|
mut res := CheckResult{}
|
2021-01-10 16:58:45 +03:00
|
|
|
|
if term_colors {
|
|
|
|
|
os.setenv('VCOLORS', 'always', true)
|
|
|
|
|
}
|
2022-06-30 13:49:47 +03:00
|
|
|
|
os.mkdir_all(vcheckfolder, mode: 0o700) or {} // keep directory private
|
2022-03-27 10:24:10 +03:00
|
|
|
|
defer {
|
|
|
|
|
os.rmdir_all(vcheckfolder) or {}
|
|
|
|
|
}
|
2021-02-05 18:46:20 +03:00
|
|
|
|
for i := 0; i < files_paths.len; i++ {
|
|
|
|
|
file_path := files_paths[i]
|
|
|
|
|
if os.is_dir(file_path) {
|
|
|
|
|
files_paths << md_file_paths(file_path)
|
|
|
|
|
continue
|
|
|
|
|
}
|
2020-03-20 18:41:18 +03:00
|
|
|
|
real_path := os.real_path(file_path)
|
2020-02-20 15:41:03 +03:00
|
|
|
|
lines := os.read_lines(real_path) or {
|
2022-11-15 16:53:13 +03:00
|
|
|
|
println('"${file_path}" does not exist')
|
2021-03-10 18:46:07 +03:00
|
|
|
|
res.warnings++
|
2020-02-20 15:41:03 +03:00
|
|
|
|
continue
|
|
|
|
|
}
|
2020-11-03 03:04:14 +03:00
|
|
|
|
mut mdfile := MDFile{
|
|
|
|
|
path: file_path
|
2021-03-10 18:46:07 +03:00
|
|
|
|
lines: lines
|
2020-11-03 03:04:14 +03:00
|
|
|
|
}
|
2021-03-10 18:46:07 +03:00
|
|
|
|
res += mdfile.check()
|
2020-02-20 15:41:03 +03:00
|
|
|
|
}
|
2021-04-12 15:51:28 +03:00
|
|
|
|
if res.errors == 0 && show_progress {
|
2022-01-31 23:51:04 +03:00
|
|
|
|
clear_previous_line()
|
2021-04-12 15:51:28 +03:00
|
|
|
|
}
|
2021-03-10 18:46:07 +03:00
|
|
|
|
if res.warnings > 0 || res.errors > 0 || res.oks > 0 {
|
2022-11-15 16:53:13 +03:00
|
|
|
|
println('\nWarnings: ${res.warnings} | Errors: ${res.errors} | OKs: ${res.oks}')
|
2020-10-20 21:14:56 +03:00
|
|
|
|
}
|
2021-03-10 18:46:07 +03:00
|
|
|
|
if res.errors > 0 {
|
2020-02-20 15:41:03 +03:00
|
|
|
|
exit(1)
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-11-03 03:04:14 +03:00
|
|
|
|
|
2021-02-05 18:46:20 +03:00
|
|
|
|
fn md_file_paths(dir string) []string {
|
2020-11-18 20:28:28 +03:00
|
|
|
|
mut files_to_check := []string{}
|
2021-02-05 18:46:20 +03:00
|
|
|
|
md_files := os.walk_ext(dir, '.md')
|
2020-11-18 20:28:28 +03:00
|
|
|
|
for file in md_files {
|
2023-01-30 19:55:56 +03:00
|
|
|
|
nfile := file.replace('\\', '/')
|
|
|
|
|
if nfile.contains_any_substr(['/thirdparty/', 'CHANGELOG']) {
|
2020-12-22 23:01:54 +03:00
|
|
|
|
continue
|
|
|
|
|
}
|
2020-11-18 20:28:28 +03:00
|
|
|
|
files_to_check << file
|
|
|
|
|
}
|
|
|
|
|
return files_to_check
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-05 18:46:20 +03:00
|
|
|
|
fn wprintln(s string) {
|
|
|
|
|
if !hide_warnings {
|
|
|
|
|
println(s)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-03 03:04:14 +03:00
|
|
|
|
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 {
|
2022-11-15 16:53:13 +03:00
|
|
|
|
return btext('${file_path}:${lnumber + 1}:${column + 1}:') + btext(mtext(' warn:')) +
|
|
|
|
|
rtext(' ${message}')
|
2020-11-03 03:04:14 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn eline(file_path string, lnumber int, column int, message string) string {
|
2022-11-15 16:53:13 +03:00
|
|
|
|
return btext('${file_path}:${lnumber + 1}:${column + 1}:') + btext(rtext(' error: ${message}'))
|
2020-11-03 03:04:14 +03:00
|
|
|
|
}
|
|
|
|
|
|
2022-01-31 23:51:04 +03:00
|
|
|
|
const default_command = 'compile'
|
2020-11-03 03:04:14 +03:00
|
|
|
|
|
|
|
|
|
struct VCodeExample {
|
|
|
|
|
mut:
|
|
|
|
|
text []string
|
|
|
|
|
command string
|
|
|
|
|
sline int
|
|
|
|
|
eline int
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum MDFileParserState {
|
|
|
|
|
markdown
|
|
|
|
|
vexample
|
2020-11-18 20:28:28 +03:00
|
|
|
|
codeblock
|
2020-11-03 03:04:14 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct MDFile {
|
2022-10-26 21:47:36 +03:00
|
|
|
|
path string
|
2020-11-03 03:04:14 +03:00
|
|
|
|
mut:
|
2022-10-26 21:47:36 +03:00
|
|
|
|
lines []string
|
2020-11-03 03:04:14 +03:00
|
|
|
|
examples []VCodeExample
|
|
|
|
|
current VCodeExample
|
|
|
|
|
state MDFileParserState = .markdown
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-12 15:51:28 +03:00
|
|
|
|
fn (mut f MDFile) progress(message string) {
|
|
|
|
|
if show_progress {
|
2022-01-31 23:51:04 +03:00
|
|
|
|
clear_previous_line()
|
2022-11-15 16:53:13 +03:00
|
|
|
|
println('File: ${f.path:-30s}, Lines: ${f.lines.len:5}, ${message}')
|
2021-04-12 15:51:28 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-10 18:46:07 +03:00
|
|
|
|
fn (mut f MDFile) check() CheckResult {
|
|
|
|
|
mut res := CheckResult{}
|
2021-06-14 13:12:02 +03:00
|
|
|
|
mut anchor_data := AnchorData{}
|
2021-03-10 18:46:07 +03:00
|
|
|
|
for j, line in f.lines {
|
2021-04-12 15:51:28 +03:00
|
|
|
|
// f.progress('line: $j')
|
2021-08-16 10:11:44 +03:00
|
|
|
|
if f.state == .vexample {
|
|
|
|
|
if line.len > too_long_line_length_example {
|
2022-11-15 16:53:13 +03:00
|
|
|
|
wprintln(wline(f.path, j, line.len, 'example lines must be less than ${too_long_line_length_example} characters'))
|
2021-03-10 18:46:07 +03:00
|
|
|
|
wprintln(line)
|
|
|
|
|
res.warnings++
|
2021-08-16 10:11:44 +03:00
|
|
|
|
}
|
|
|
|
|
} else if f.state == .codeblock {
|
|
|
|
|
if line.len > too_long_line_length_codeblock {
|
2022-11-15 16:53:13 +03:00
|
|
|
|
wprintln(wline(f.path, j, line.len, 'code lines must be less than ${too_long_line_length_codeblock} characters'))
|
2021-03-10 18:46:07 +03:00
|
|
|
|
wprintln(line)
|
|
|
|
|
res.warnings++
|
2021-08-16 10:11:44 +03:00
|
|
|
|
}
|
|
|
|
|
} else if line.starts_with('|') {
|
|
|
|
|
if line.len > too_long_line_length_table {
|
2022-11-15 16:53:13 +03:00
|
|
|
|
wprintln(wline(f.path, j, line.len, 'table lines must be less than ${too_long_line_length_table} characters'))
|
2021-03-10 18:46:07 +03:00
|
|
|
|
wprintln(line)
|
|
|
|
|
res.warnings++
|
2021-08-16 10:11:44 +03:00
|
|
|
|
}
|
|
|
|
|
} else if line.contains('http') {
|
|
|
|
|
if line.all_after('https').len > too_long_line_length_link {
|
2022-11-15 16:53:13 +03:00
|
|
|
|
wprintln(wline(f.path, j, line.len, 'link lines must be less than ${too_long_line_length_link} characters'))
|
2021-03-10 18:46:07 +03:00
|
|
|
|
wprintln(line)
|
|
|
|
|
res.warnings++
|
|
|
|
|
}
|
2021-08-16 10:11:44 +03:00
|
|
|
|
} else if line.len > too_long_line_length_other {
|
2022-11-15 16:53:13 +03:00
|
|
|
|
eprintln(eline(f.path, j, line.len, 'must be less than ${too_long_line_length_other} characters'))
|
2021-08-16 10:11:44 +03:00
|
|
|
|
eprintln(line)
|
|
|
|
|
res.errors++
|
2021-03-10 18:46:07 +03:00
|
|
|
|
}
|
2021-06-14 13:12:02 +03:00
|
|
|
|
if f.state == .markdown {
|
|
|
|
|
anchor_data.add_links(j, line)
|
|
|
|
|
anchor_data.add_link_targets(j, line)
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-10 18:46:07 +03:00
|
|
|
|
f.parse_line(j, line)
|
|
|
|
|
}
|
2021-06-14 13:12:02 +03:00
|
|
|
|
anchor_data.check_link_target_match(f.path, mut res)
|
2021-03-10 18:46:07 +03:00
|
|
|
|
res += f.check_examples()
|
|
|
|
|
return res
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-03 03:04:14 +03:00
|
|
|
|
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
|
2021-02-05 21:25:27 +03:00
|
|
|
|
} else if command == 'nofmt' {
|
2022-11-15 16:53:13 +03:00
|
|
|
|
command += ' ${default_command}'
|
2020-11-03 03:04:14 +03:00
|
|
|
|
}
|
|
|
|
|
f.current = VCodeExample{
|
|
|
|
|
sline: lnumber
|
|
|
|
|
command: command
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
2020-11-18 20:28:28 +03:00
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-11-03 03:04:14 +03:00
|
|
|
|
}
|
|
|
|
|
if f.state == .vexample {
|
|
|
|
|
f.current.text << line
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-14 13:12:02 +03:00
|
|
|
|
struct Headline {
|
|
|
|
|
line int
|
|
|
|
|
lable string
|
|
|
|
|
level int
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct Anchor {
|
|
|
|
|
line int
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type AnchorTarget = Anchor | Headline
|
|
|
|
|
|
|
|
|
|
struct AnchorLink {
|
|
|
|
|
line int
|
|
|
|
|
lable string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct AnchorData {
|
|
|
|
|
mut:
|
|
|
|
|
links map[string][]AnchorLink
|
|
|
|
|
anchors map[string][]AnchorTarget
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn (mut ad AnchorData) add_links(line_number int, line string) {
|
2021-06-17 12:53:25 +03:00
|
|
|
|
query := r'\[(?P<lable>[^\]]+)\]\(\s*#(?P<link>[a-z0-9\-\_\x7f-\uffff]+)\)'
|
2021-06-14 13:12:02 +03:00
|
|
|
|
mut re := regex.regex_opt(query) or { panic(err) }
|
|
|
|
|
res := re.find_all_str(line)
|
|
|
|
|
|
|
|
|
|
for elem in res {
|
|
|
|
|
re.match_string(elem)
|
|
|
|
|
link := re.get_group_by_name(elem, 'link')
|
|
|
|
|
ad.links[link] << AnchorLink{
|
|
|
|
|
line: line_number
|
|
|
|
|
lable: re.get_group_by_name(elem, 'lable')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn (mut ad AnchorData) add_link_targets(line_number int, line string) {
|
|
|
|
|
if line.trim_space().starts_with('#') {
|
|
|
|
|
if headline_start_pos := line.index(' ') {
|
|
|
|
|
headline := line.substr(headline_start_pos + 1, line.len)
|
|
|
|
|
link := create_ref_link(headline)
|
|
|
|
|
ad.anchors[link] << Headline{
|
|
|
|
|
line: line_number
|
|
|
|
|
lable: headline
|
|
|
|
|
level: headline_start_pos
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2021-07-22 17:02:11 +03:00
|
|
|
|
query := '<a\\s*id=["\'](?P<link>[a-z0-9\\-\\_\\x7f-\\uffff]+)["\']\\s*/>'
|
2021-06-14 13:12:02 +03:00
|
|
|
|
mut re := regex.regex_opt(query) or { panic(err) }
|
|
|
|
|
res := re.find_all_str(line)
|
|
|
|
|
|
|
|
|
|
for elem in res {
|
|
|
|
|
re.match_string(elem)
|
|
|
|
|
link := re.get_group_by_name(elem, 'link')
|
|
|
|
|
ad.anchors[link] << Anchor{
|
|
|
|
|
line: line_number
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn (mut ad AnchorData) check_link_target_match(fpath string, mut res CheckResult) {
|
|
|
|
|
mut checked_headlines := []string{}
|
|
|
|
|
mut found_error_warning := false
|
|
|
|
|
for link, linkdata in ad.links {
|
|
|
|
|
if link in ad.anchors {
|
|
|
|
|
checked_headlines << link
|
|
|
|
|
if ad.anchors[link].len > 1 {
|
|
|
|
|
found_error_warning = true
|
|
|
|
|
res.errors++
|
|
|
|
|
for anchordata in ad.anchors[link] {
|
2022-11-15 16:53:13 +03:00
|
|
|
|
eprintln(eline(fpath, anchordata.line, 0, 'multiple link targets of existing link (#${link})'))
|
2021-06-14 13:12:02 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
found_error_warning = true
|
|
|
|
|
res.errors++
|
|
|
|
|
for brokenlink in linkdata {
|
2022-11-15 16:53:13 +03:00
|
|
|
|
eprintln(eline(fpath, brokenlink.line, 0, 'no link target found for existing link [${brokenlink.lable}](#${link})'))
|
2021-06-14 13:12:02 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for link, anchor_lists in ad.anchors {
|
2021-08-29 11:55:18 +03:00
|
|
|
|
if link !in checked_headlines {
|
2021-06-14 13:12:02 +03:00
|
|
|
|
if anchor_lists.len > 1 {
|
|
|
|
|
for anchor in anchor_lists {
|
|
|
|
|
line := match anchor {
|
|
|
|
|
Headline {
|
|
|
|
|
anchor.line
|
|
|
|
|
}
|
|
|
|
|
Anchor {
|
|
|
|
|
anchor.line
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-11-15 16:53:13 +03:00
|
|
|
|
wprintln(wline(fpath, line, 0, 'multiple link target for non existing link (#${link})'))
|
2021-06-14 13:12:02 +03:00
|
|
|
|
found_error_warning = true
|
|
|
|
|
res.warnings++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if found_error_warning {
|
|
|
|
|
eprintln('') // fix suppressed last error output
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-17 12:53:25 +03:00
|
|
|
|
// based on a reference sample md doc
|
|
|
|
|
// https://github.com/aheissenberger/vlang-markdown-module/blob/master/test.md
|
2021-06-14 13:12:02 +03:00
|
|
|
|
fn create_ref_link(s string) string {
|
2021-06-17 12:53:25 +03:00
|
|
|
|
mut result := ''
|
|
|
|
|
for c in s.trim_space() {
|
|
|
|
|
result += match c {
|
|
|
|
|
`a`...`z`, `0`...`9` {
|
|
|
|
|
c.ascii_str()
|
|
|
|
|
}
|
|
|
|
|
`A`...`Z` {
|
|
|
|
|
c.ascii_str().to_lower()
|
|
|
|
|
}
|
|
|
|
|
` `, `-` {
|
|
|
|
|
'-'
|
|
|
|
|
}
|
|
|
|
|
`_` {
|
|
|
|
|
'_'
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
if c > 127 { c.ascii_str() } else { '' }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result
|
2021-06-14 13:12:02 +03:00
|
|
|
|
}
|
|
|
|
|
|
2021-03-06 20:09:28 +03:00
|
|
|
|
fn (mut f MDFile) debug() {
|
2020-11-03 03:04:14 +03:00
|
|
|
|
for e in f.examples {
|
2022-11-15 16:53:13 +03:00
|
|
|
|
eprintln('f.path: ${f.path} | example: ${e}')
|
2020-11-03 03:04:14 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-10 16:58:45 +03:00
|
|
|
|
fn cmdexecute(cmd string) int {
|
2022-01-31 23:51:04 +03:00
|
|
|
|
verbose_println(cmd)
|
2021-03-08 21:52:13 +03:00
|
|
|
|
res := os.execute(cmd)
|
|
|
|
|
if res.exit_code < 0 {
|
|
|
|
|
return 1
|
|
|
|
|
}
|
2021-01-10 16:58:45 +03:00
|
|
|
|
if res.exit_code != 0 {
|
|
|
|
|
eprint(res.output)
|
|
|
|
|
}
|
|
|
|
|
return res.exit_code
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn silent_cmdexecute(cmd string) int {
|
2022-01-31 23:51:04 +03:00
|
|
|
|
verbose_println(cmd)
|
2021-03-08 21:52:13 +03:00
|
|
|
|
res := os.execute(cmd)
|
2021-01-07 22:13:07 +03:00
|
|
|
|
return res.exit_code
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-10 16:58:45 +03:00
|
|
|
|
fn get_fmt_exit_code(vfile string, vexe string) int {
|
2022-01-22 22:56:01 +03:00
|
|
|
|
return silent_cmdexecute('${os.quoted_path(vexe)} fmt -verify ${os.quoted_path(vfile)}')
|
2021-01-10 16:58:45 +03:00
|
|
|
|
}
|
|
|
|
|
|
2021-03-10 18:46:07 +03:00
|
|
|
|
fn (mut f MDFile) check_examples() CheckResult {
|
2020-11-03 03:04:14 +03:00
|
|
|
|
mut errors := 0
|
|
|
|
|
mut oks := 0
|
2022-10-26 21:47:36 +03:00
|
|
|
|
recheck_all_examples: for e in f.examples {
|
2020-11-03 03:04:14 +03:00
|
|
|
|
if e.command == 'ignore' {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if e.command == 'wip' {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
fname := os.base(f.path).replace('.md', '_md')
|
|
|
|
|
uid := rand.ulid()
|
2022-03-27 10:24:10 +03:00
|
|
|
|
cfile := os.join_path(vcheckfolder, '${uid}.c')
|
|
|
|
|
vfile := os.join_path(vcheckfolder, 'check_${fname}_example_${e.sline}__${e.eline}__${uid}.v')
|
|
|
|
|
efile := os.join_path(vcheckfolder, 'check_${fname}_example_${e.sline}__${e.eline}__${uid}.exe')
|
2020-11-03 03:04:14 +03:00
|
|
|
|
mut should_cleanup_vfile := true
|
|
|
|
|
// eprintln('>>> checking example $vfile ...')
|
2020-12-06 00:54:41 +03:00
|
|
|
|
vcontent := e.text.join('\n') + '\n'
|
2021-03-01 02:18:14 +03:00
|
|
|
|
os.write_file(vfile, vcontent) or { panic(err) }
|
2020-11-03 03:04:14 +03:00
|
|
|
|
mut acommands := e.command.split(' ')
|
2020-12-06 00:54:41 +03:00
|
|
|
|
nofmt := 'nofmt' in acommands
|
2020-11-03 03:04:14 +03:00
|
|
|
|
for command in acommands {
|
2022-11-15 16:53:13 +03:00
|
|
|
|
f.progress('example from ${e.sline} to ${e.eline}, command: ${command}')
|
2021-01-07 22:13:07 +03:00
|
|
|
|
fmt_res := if nofmt { 0 } else { get_fmt_exit_code(vfile, vexe) }
|
2020-11-03 03:04:14 +03:00
|
|
|
|
match command {
|
|
|
|
|
'compile' {
|
2022-03-27 10:24:10 +03:00
|
|
|
|
res := cmdexecute('${os.quoted_path(vexe)} -w -Wfatal-errors -o ${os.quoted_path(efile)} ${os.quoted_path(vfile)}')
|
2020-12-06 00:54:41 +03:00
|
|
|
|
if res != 0 || fmt_res != 0 {
|
|
|
|
|
if res != 0 {
|
|
|
|
|
eprintln(eline(f.path, e.sline, 0, 'example failed to compile'))
|
|
|
|
|
}
|
2022-10-26 21:47:36 +03:00
|
|
|
|
f.report_not_formatted_example_if_needed(e, fmt_res, vfile) or {
|
|
|
|
|
unsafe {
|
|
|
|
|
goto recheck_all_examples
|
|
|
|
|
}
|
2020-12-06 00:54:41 +03:00
|
|
|
|
}
|
2020-11-03 03:04:14 +03:00
|
|
|
|
eprintln(vcontent)
|
|
|
|
|
should_cleanup_vfile = false
|
|
|
|
|
errors++
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
oks++
|
|
|
|
|
}
|
2022-01-31 23:51:04 +03:00
|
|
|
|
'cgen' {
|
|
|
|
|
res := cmdexecute('${os.quoted_path(vexe)} -w -Wfatal-errors -o ${os.quoted_path(cfile)} ${os.quoted_path(vfile)}')
|
|
|
|
|
if res != 0 || fmt_res != 0 {
|
|
|
|
|
if res != 0 {
|
|
|
|
|
eprintln(eline(f.path, e.sline, 0, 'example failed to generate C code'))
|
|
|
|
|
}
|
2022-10-26 21:47:36 +03:00
|
|
|
|
f.report_not_formatted_example_if_needed(e, fmt_res, vfile) or {
|
|
|
|
|
unsafe {
|
|
|
|
|
goto recheck_all_examples
|
|
|
|
|
}
|
2022-01-31 23:51:04 +03:00
|
|
|
|
}
|
|
|
|
|
eprintln(vcontent)
|
|
|
|
|
should_cleanup_vfile = false
|
|
|
|
|
errors++
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
oks++
|
|
|
|
|
}
|
2021-07-20 08:31:20 +03:00
|
|
|
|
'globals' {
|
2022-01-31 23:51:04 +03:00
|
|
|
|
res := cmdexecute('${os.quoted_path(vexe)} -w -Wfatal-errors -enable-globals -o ${os.quoted_path(cfile)} ${os.quoted_path(vfile)}')
|
2021-07-20 08:31:20 +03:00
|
|
|
|
if res != 0 || fmt_res != 0 {
|
|
|
|
|
if res != 0 {
|
|
|
|
|
eprintln(eline(f.path, e.sline, 0, '`example failed to compile with -enable-globals'))
|
|
|
|
|
}
|
2022-10-26 21:47:36 +03:00
|
|
|
|
f.report_not_formatted_example_if_needed(e, fmt_res, vfile) or {
|
|
|
|
|
unsafe {
|
|
|
|
|
goto recheck_all_examples
|
|
|
|
|
}
|
2021-07-20 08:31:20 +03:00
|
|
|
|
}
|
|
|
|
|
eprintln(vcontent)
|
|
|
|
|
should_cleanup_vfile = false
|
|
|
|
|
errors++
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
oks++
|
|
|
|
|
}
|
2020-11-14 21:02:10 +03:00
|
|
|
|
'live' {
|
2022-01-31 23:51:04 +03:00
|
|
|
|
res := cmdexecute('${os.quoted_path(vexe)} -w -Wfatal-errors -live -o ${os.quoted_path(cfile)} ${os.quoted_path(vfile)}')
|
2020-12-06 00:54:41 +03:00
|
|
|
|
if res != 0 || fmt_res != 0 {
|
|
|
|
|
if res != 0 {
|
|
|
|
|
eprintln(eline(f.path, e.sline, 0, 'example failed to compile with -live'))
|
|
|
|
|
}
|
2022-10-26 21:47:36 +03:00
|
|
|
|
f.report_not_formatted_example_if_needed(e, fmt_res, vfile) or {
|
|
|
|
|
unsafe {
|
|
|
|
|
goto recheck_all_examples
|
|
|
|
|
}
|
2020-12-06 00:54:41 +03:00
|
|
|
|
}
|
2020-11-14 21:02:10 +03:00
|
|
|
|
eprintln(vcontent)
|
|
|
|
|
should_cleanup_vfile = false
|
|
|
|
|
errors++
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
oks++
|
|
|
|
|
}
|
2022-11-04 16:35:25 +03:00
|
|
|
|
'shared' {
|
|
|
|
|
res := cmdexecute('${os.quoted_path(vexe)} -w -Wfatal-errors -shared -o ${os.quoted_path(cfile)} ${os.quoted_path(vfile)}')
|
|
|
|
|
if res != 0 || fmt_res != 0 {
|
|
|
|
|
if res != 0 {
|
|
|
|
|
eprintln(eline(f.path, e.sline, 0, 'module example failed to compile with -shared'))
|
|
|
|
|
}
|
|
|
|
|
f.report_not_formatted_example_if_needed(e, fmt_res, vfile) or {
|
|
|
|
|
unsafe {
|
|
|
|
|
goto recheck_all_examples
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
eprintln(vcontent)
|
|
|
|
|
should_cleanup_vfile = false
|
|
|
|
|
errors++
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
oks++
|
|
|
|
|
}
|
2020-11-03 03:04:14 +03:00
|
|
|
|
'failcompile' {
|
2022-01-31 23:51:04 +03:00
|
|
|
|
res := silent_cmdexecute('${os.quoted_path(vexe)} -w -Wfatal-errors -o ${os.quoted_path(cfile)} ${os.quoted_path(vfile)}')
|
2021-04-12 14:59:27 +03:00
|
|
|
|
if res == 0 || fmt_res != 0 {
|
|
|
|
|
if res == 0 {
|
|
|
|
|
eprintln(eline(f.path, e.sline, 0, '`failcompile` example compiled'))
|
|
|
|
|
}
|
2022-10-26 21:47:36 +03:00
|
|
|
|
f.report_not_formatted_example_if_needed(e, fmt_res, vfile) or {
|
|
|
|
|
unsafe {
|
|
|
|
|
goto recheck_all_examples
|
|
|
|
|
}
|
2021-04-12 14:59:27 +03:00
|
|
|
|
}
|
2020-11-03 03:04:14 +03:00
|
|
|
|
eprintln(vcontent)
|
|
|
|
|
should_cleanup_vfile = false
|
|
|
|
|
errors++
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
oks++
|
|
|
|
|
}
|
|
|
|
|
'oksyntax' {
|
2022-01-22 22:56:01 +03:00
|
|
|
|
res := cmdexecute('${os.quoted_path(vexe)} -w -Wfatal-errors -check-syntax ${os.quoted_path(vfile)}')
|
2020-12-06 00:54:41 +03:00
|
|
|
|
if res != 0 || fmt_res != 0 {
|
|
|
|
|
if res != 0 {
|
|
|
|
|
eprintln(eline(f.path, e.sline, 0, '`oksyntax` example with invalid syntax'))
|
|
|
|
|
}
|
2022-10-26 21:47:36 +03:00
|
|
|
|
f.report_not_formatted_example_if_needed(e, fmt_res, vfile) or {
|
|
|
|
|
unsafe {
|
|
|
|
|
goto recheck_all_examples
|
|
|
|
|
}
|
2020-12-06 00:54:41 +03:00
|
|
|
|
}
|
2020-11-03 03:04:14 +03:00
|
|
|
|
eprintln(vcontent)
|
|
|
|
|
should_cleanup_vfile = false
|
|
|
|
|
errors++
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
oks++
|
|
|
|
|
}
|
2022-09-25 19:36:01 +03:00
|
|
|
|
'okfmt' {
|
|
|
|
|
if fmt_res != 0 {
|
2022-10-26 21:47:36 +03:00
|
|
|
|
f.report_not_formatted_example_if_needed(e, fmt_res, vfile) or {
|
|
|
|
|
unsafe {
|
|
|
|
|
goto recheck_all_examples
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-09-25 19:36:01 +03:00
|
|
|
|
eprintln(vcontent)
|
|
|
|
|
should_cleanup_vfile = false
|
|
|
|
|
errors++
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
oks++
|
|
|
|
|
}
|
2020-11-03 03:04:14 +03:00
|
|
|
|
'badsyntax' {
|
2022-01-22 22:56:01 +03:00
|
|
|
|
res := silent_cmdexecute('${os.quoted_path(vexe)} -w -Wfatal-errors -check-syntax ${os.quoted_path(vfile)}')
|
2020-11-03 03:04:14 +03:00
|
|
|
|
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++
|
|
|
|
|
}
|
2020-12-06 00:54:41 +03:00
|
|
|
|
'nofmt' {}
|
2023-02-18 21:55:10 +03:00
|
|
|
|
// mark the example as playable inside docs
|
|
|
|
|
'play' {}
|
|
|
|
|
// same as play, but run example as a test
|
|
|
|
|
'play-test' {}
|
2020-11-03 03:04:14 +03:00
|
|
|
|
else {
|
2022-11-15 16:53:13 +03:00
|
|
|
|
eprintln(eline(f.path, e.sline, 0, 'unrecognized command: "${command}", use one of: wip/ignore/compile/failcompile/okfmt/nofmt/oksyntax/badsyntax/cgen/globals/live/shared'))
|
2020-11-03 03:04:14 +03:00
|
|
|
|
should_cleanup_vfile = false
|
|
|
|
|
errors++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-03-27 10:24:10 +03:00
|
|
|
|
os.rm(cfile) or {}
|
|
|
|
|
os.rm(efile) or {}
|
2020-11-03 03:04:14 +03:00
|
|
|
|
if should_cleanup_vfile {
|
2021-03-01 02:18:14 +03:00
|
|
|
|
os.rm(vfile) or { panic(err) }
|
2020-11-03 03:04:14 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
2021-03-10 18:46:07 +03:00
|
|
|
|
return CheckResult{
|
|
|
|
|
errors: errors
|
|
|
|
|
oks: oks
|
|
|
|
|
}
|
2020-11-03 03:04:14 +03:00
|
|
|
|
}
|
2022-01-31 23:51:04 +03:00
|
|
|
|
|
|
|
|
|
fn verbose_println(message string) {
|
|
|
|
|
if is_verbose {
|
|
|
|
|
println(message)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn clear_previous_line() {
|
|
|
|
|
if is_verbose {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
term.clear_previous_line()
|
|
|
|
|
}
|
2022-10-26 21:47:36 +03:00
|
|
|
|
|
|
|
|
|
fn (mut f MDFile) report_not_formatted_example_if_needed(e VCodeExample, fmt_res int, vfile string) ! {
|
|
|
|
|
if fmt_res == 0 {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
eprintln(eline(f.path, e.sline, 0, 'example is not formatted'))
|
|
|
|
|
if !should_autofix {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
f.autofix_example(e, vfile) or {
|
|
|
|
|
if err is ExampleWasRewritten {
|
2022-11-15 16:53:13 +03:00
|
|
|
|
eprintln('>> f.path: ${f.path} | example from ${e.sline} to ${e.eline} was re-formated by vfmt')
|
2022-10-26 21:47:36 +03:00
|
|
|
|
return err
|
|
|
|
|
}
|
2022-11-15 16:53:13 +03:00
|
|
|
|
eprintln('>> f.path: ${f.path} | encountered error while autofixing the example: ${err}')
|
2022-10-26 21:47:36 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct ExampleWasRewritten {
|
|
|
|
|
Error
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn (mut f MDFile) autofix_example(e VCodeExample, vfile string) ! {
|
2022-11-15 16:53:13 +03:00
|
|
|
|
eprintln('>>> AUTOFIXING f.path: ${f.path} | e.sline: ${e.sline} | vfile: ${vfile}')
|
2022-10-26 21:47:36 +03:00
|
|
|
|
res := cmdexecute('${os.quoted_path(vexe)} fmt -w ${os.quoted_path(vfile)}')
|
|
|
|
|
if res != 0 {
|
|
|
|
|
return error('could not autoformat the example')
|
|
|
|
|
}
|
|
|
|
|
formatted_content_lines := os.read_lines(vfile) or { return }
|
|
|
|
|
mut new_lines := []string{}
|
|
|
|
|
new_lines << f.lines#[0..e.sline + 1]
|
|
|
|
|
new_lines << formatted_content_lines
|
|
|
|
|
new_lines << f.lines#[e.eline..]
|
|
|
|
|
f.update_examples(new_lines)!
|
|
|
|
|
os.rm(vfile) or {}
|
2022-10-26 22:45:58 +03:00
|
|
|
|
f.examples = f.examples.filter(it.sline >= e.sline)
|
2022-10-26 21:47:36 +03:00
|
|
|
|
return ExampleWasRewritten{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn (mut f MDFile) update_examples(new_lines []string) ! {
|
|
|
|
|
os.write_file(f.path, new_lines.join('\n'))!
|
|
|
|
|
f.lines = new_lines
|
|
|
|
|
f.examples = []
|
|
|
|
|
f.current = VCodeExample{}
|
|
|
|
|
f.state = .markdown
|
|
|
|
|
for j, line in f.lines {
|
|
|
|
|
f.parse_line(j, line)
|
|
|
|
|
}
|
|
|
|
|
}
|