tools: improve `v check-md` by checking for broken TOC headline links (#10417)
parent
90d04b0ce6
commit
7983495c57
|
@ -9,6 +9,7 @@ import rand
|
||||||
import term
|
import term
|
||||||
import vhelp
|
import vhelp
|
||||||
import v.pref
|
import v.pref
|
||||||
|
import regex
|
||||||
|
|
||||||
const (
|
const (
|
||||||
too_long_line_length = 100
|
too_long_line_length = 100
|
||||||
|
@ -162,6 +163,7 @@ fn (mut f MDFile) progress(message string) {
|
||||||
|
|
||||||
fn (mut f MDFile) check() CheckResult {
|
fn (mut f MDFile) check() CheckResult {
|
||||||
mut res := CheckResult{}
|
mut res := CheckResult{}
|
||||||
|
mut anchor_data := AnchorData{}
|
||||||
for j, line in f.lines {
|
for j, line in f.lines {
|
||||||
// f.progress('line: $j')
|
// f.progress('line: $j')
|
||||||
if line.len > too_long_line_length {
|
if line.len > too_long_line_length {
|
||||||
|
@ -187,8 +189,14 @@ fn (mut f MDFile) check() CheckResult {
|
||||||
res.errors++
|
res.errors++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if f.state == .markdown {
|
||||||
|
anchor_data.add_links(j, line)
|
||||||
|
anchor_data.add_link_targets(j, line)
|
||||||
|
}
|
||||||
|
|
||||||
f.parse_line(j, line)
|
f.parse_line(j, line)
|
||||||
}
|
}
|
||||||
|
anchor_data.check_link_target_match(f.path, mut res)
|
||||||
res += f.check_examples()
|
res += f.check_examples()
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
@ -234,6 +242,121 @@ fn (mut f MDFile) parse_line(lnumber int, line string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
query := r'\[(?P<lable>[^\]]+)\]\(\s*#(?P<link>[a-z\-]+)\)'
|
||||||
|
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 {
|
||||||
|
query := r'<a\s*id=["\'](?P<link>[a-z\-]+)["\']\s*/>'
|
||||||
|
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] {
|
||||||
|
eprintln(eline(fpath, anchordata.line, 0, 'multiple link targets of existing link (#$link)'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
found_error_warning = true
|
||||||
|
res.errors++
|
||||||
|
for brokenlink in linkdata {
|
||||||
|
eprintln(eline(fpath, brokenlink.line, 0, 'no link target found for existing link [$brokenlink.lable](#$link)'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for link, anchor_lists in ad.anchors {
|
||||||
|
if !(link in checked_headlines) {
|
||||||
|
if anchor_lists.len > 1 {
|
||||||
|
for anchor in anchor_lists {
|
||||||
|
line := match anchor {
|
||||||
|
Headline {
|
||||||
|
anchor.line
|
||||||
|
}
|
||||||
|
Anchor {
|
||||||
|
anchor.line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wprintln(wline(fpath, line, 0, 'multiple link target for non existing link (#$link)'))
|
||||||
|
found_error_warning = true
|
||||||
|
res.warnings++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if found_error_warning {
|
||||||
|
eprintln('') // fix suppressed last error output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_ref_link(s string) string {
|
||||||
|
query_remove := r'[^a-z \-]'
|
||||||
|
mut re := regex.regex_opt(query_remove) or { panic(err) }
|
||||||
|
return re.replace_simple(s.to_lower(), '').replace(' ', '-')
|
||||||
|
}
|
||||||
|
|
||||||
fn (mut f MDFile) debug() {
|
fn (mut f MDFile) debug() {
|
||||||
for e in f.examples {
|
for e in f.examples {
|
||||||
eprintln('f.path: $f.path | example: $e')
|
eprintln('f.path: $f.path | example: $e')
|
||||||
|
|
20
doc/docs.md
20
doc/docs.md
|
@ -83,7 +83,7 @@ For more details and troubleshooting, please visit the [vab GitHub repository](h
|
||||||
* [Structs](#structs)
|
* [Structs](#structs)
|
||||||
* [Embedded structs](#embedded-structs)
|
* [Embedded structs](#embedded-structs)
|
||||||
* [Default field values](#default-field-values)
|
* [Default field values](#default-field-values)
|
||||||
* [Short struct literal syntax](#short-struct-initialization-syntax)
|
* [Short struct literal syntax](#short-struct-literal-syntax)
|
||||||
* [Access modifiers](#access-modifiers)
|
* [Access modifiers](#access-modifiers)
|
||||||
* [Methods](#methods)
|
* [Methods](#methods)
|
||||||
* [Unions](#unions)
|
* [Unions](#unions)
|
||||||
|
@ -2270,7 +2270,7 @@ You can also install modules already created by someone else with [VPM](https://
|
||||||
```powershell
|
```powershell
|
||||||
v install [module]
|
v install [module]
|
||||||
```
|
```
|
||||||
###### Example:
|
**Example:**
|
||||||
```powershell
|
```powershell
|
||||||
v install ui
|
v install ui
|
||||||
```
|
```
|
||||||
|
@ -2280,7 +2280,7 @@ Removing a module with v:
|
||||||
```powershell
|
```powershell
|
||||||
v remove [module]
|
v remove [module]
|
||||||
```
|
```
|
||||||
###### Example:
|
**Example:**
|
||||||
```powershell
|
```powershell
|
||||||
v remove ui
|
v remove ui
|
||||||
```
|
```
|
||||||
|
@ -2290,7 +2290,7 @@ Updating an installed module from [VPM](https://vpm.vlang.io/):
|
||||||
```powershell
|
```powershell
|
||||||
v update [module]
|
v update [module]
|
||||||
```
|
```
|
||||||
###### Example:
|
**Example:**
|
||||||
```powershell
|
```powershell
|
||||||
v update ui
|
v update ui
|
||||||
```
|
```
|
||||||
|
@ -2305,7 +2305,7 @@ To see all the modules you have installed, you can use:
|
||||||
```powershell
|
```powershell
|
||||||
v list
|
v list
|
||||||
```
|
```
|
||||||
###### Example
|
**Example:**
|
||||||
```powershell
|
```powershell
|
||||||
> v list
|
> v list
|
||||||
Installed modules:
|
Installed modules:
|
||||||
|
@ -2318,7 +2318,7 @@ outdated Show installed modules that need updates.
|
||||||
```powershell
|
```powershell
|
||||||
v outdated
|
v outdated
|
||||||
```
|
```
|
||||||
###### Example
|
**Example:**
|
||||||
```powershell
|
```powershell
|
||||||
> v outdated
|
> v outdated
|
||||||
Modules are up to date.
|
Modules are up to date.
|
||||||
|
@ -4024,7 +4024,7 @@ created by the JS Backend (flag: `-b js`).
|
||||||
|
|
||||||
`$` is used as a prefix for compile-time operations.
|
`$` is used as a prefix for compile-time operations.
|
||||||
|
|
||||||
#### $if
|
#### `$if` condition
|
||||||
```v
|
```v
|
||||||
// Support for multiple conditions in one branch
|
// Support for multiple conditions in one branch
|
||||||
$if ios || android {
|
$if ios || android {
|
||||||
|
@ -4076,7 +4076,7 @@ Full list of builtin options:
|
||||||
| `gnu`, `hpux`, `haiku`, `qnx` | `cplusplus` | `big_endian` |
|
| `gnu`, `hpux`, `haiku`, `qnx` | `cplusplus` | `big_endian` |
|
||||||
| `solaris` | | | |
|
| `solaris` | | | |
|
||||||
|
|
||||||
#### $embed_file
|
#### `$embed_file`
|
||||||
|
|
||||||
```v ignore
|
```v ignore
|
||||||
import os
|
import os
|
||||||
|
@ -4099,7 +4099,7 @@ executable, increasing your binary size, but making it more self contained
|
||||||
and thus easier to distribute. In this case, `f.data()` will cause *no IO*,
|
and thus easier to distribute. In this case, `f.data()` will cause *no IO*,
|
||||||
and it will always return the same data.
|
and it will always return the same data.
|
||||||
|
|
||||||
#### $tmpl for embedding and parsing V template files
|
#### `$tmpl` for embedding and parsing V template files
|
||||||
|
|
||||||
V has a simple template language for text and html templates, and they can easily
|
V has a simple template language for text and html templates, and they can easily
|
||||||
be embedded via `$tmpl('path/to/template.txt')`:
|
be embedded via `$tmpl('path/to/template.txt')`:
|
||||||
|
@ -4149,7 +4149,7 @@ numbers: [1, 2, 3]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### $env
|
#### `$env`
|
||||||
|
|
||||||
```v
|
```v
|
||||||
module main
|
module main
|
||||||
|
|
|
@ -74,37 +74,37 @@ different capabilities:
|
||||||
| structured data types | + | + | + | |
|
| structured data types | + | + | + | |
|
||||||
|
|
||||||
### Strengths
|
### Strengths
|
||||||
#### default
|
**default**
|
||||||
- very fast
|
- very fast
|
||||||
- unlimited access from different coroutines
|
- unlimited access from different coroutines
|
||||||
- easy to handle
|
- easy to handle
|
||||||
|
|
||||||
#### `mut`
|
**`mut`**
|
||||||
- very fast
|
- very fast
|
||||||
- easy to handle
|
- easy to handle
|
||||||
|
|
||||||
#### `shared`
|
**`shared`**
|
||||||
- concurrent access from different coroutines
|
- concurrent access from different coroutines
|
||||||
- data type may be complex structure
|
- data type may be complex structure
|
||||||
- sophisticated access possible (several statements within one `lock`
|
- sophisticated access possible (several statements within one `lock`
|
||||||
block)
|
block)
|
||||||
|
|
||||||
#### `atomic`
|
**`atomic`**
|
||||||
- concurrent access from different coroutines
|
- concurrent access from different coroutines
|
||||||
- reasonably fast
|
- reasonably fast
|
||||||
|
|
||||||
### Weaknesses
|
### Weaknesses
|
||||||
#### default
|
**default**
|
||||||
- read only
|
- read only
|
||||||
|
|
||||||
#### `mut`
|
**`mut`**
|
||||||
- access only from one coroutine at a time
|
- access only from one coroutine at a time
|
||||||
|
|
||||||
#### `shared`
|
**`shared`**
|
||||||
- lock/unlock are slow
|
- lock/unlock are slow
|
||||||
- moderately difficult to handle (needs `lock` block)
|
- moderately difficult to handle (needs `lock` block)
|
||||||
|
|
||||||
#### `atomic`
|
**`atomic`**
|
||||||
- limited to single (max. 64 bit) integers (and pointers)
|
- limited to single (max. 64 bit) integers (and pointers)
|
||||||
- only a small set of predefined operations possible
|
- only a small set of predefined operations possible
|
||||||
- very difficult to handle correctly
|
- very difficult to handle correctly
|
||||||
|
@ -191,3 +191,5 @@ are sometimes surprising. Each statement should be seen as a single
|
||||||
transaction that is unrelated to the previous or following
|
transaction that is unrelated to the previous or following
|
||||||
statement. Therefore - but also for performance reasons - it's often
|
statement. Therefore - but also for performance reasons - it's often
|
||||||
better to group consecutive coherent statements in an explicit `lock` block.
|
better to group consecutive coherent statements in an explicit `lock` block.
|
||||||
|
|
||||||
|
### Channels
|
||||||
|
|
|
@ -23,7 +23,7 @@ provides V language support for Visual Studio Code.
|
||||||
[install V compiler](https://github.com/vlang/v/blob/master/doc/docs.md#install-from-source)
|
[install V compiler](https://github.com/vlang/v/blob/master/doc/docs.md#install-from-source)
|
||||||
on your operating system.
|
on your operating system.
|
||||||
|
|
||||||
### Setup
|
### Setup Extention
|
||||||
|
|
||||||
Install [V VS Code Extention](https://marketplace.visualstudio.com/items?itemName=vlanguage.vscode-vlang).
|
Install [V VS Code Extention](https://marketplace.visualstudio.com/items?itemName=vlanguage.vscode-vlang).
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ for Visual Studio Code provides visual conditional debugging.
|
||||||
[DWARF](https://en.wikipedia.org/wiki/DWARF) information to show and
|
[DWARF](https://en.wikipedia.org/wiki/DWARF) information to show and
|
||||||
edit the variable.
|
edit the variable.
|
||||||
|
|
||||||
### Setup
|
### Setup Debugging
|
||||||
|
|
||||||
1. Install the [C/C++ Extention](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools)
|
1. Install the [C/C++ Extention](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools)
|
||||||
2. Open `RUN AND DEBUG` panel (Debug Icon in left panel).
|
2. Open `RUN AND DEBUG` panel (Debug Icon in left panel).
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
This package is to generate data-driven HTML output.
|
V allows for easily using text templates, expanded at compile time to
|
||||||
|
V functions, that efficiently produce text output. This is especially
|
||||||
|
usefull for templated HTML views, but the mechanism is general enough
|
||||||
|
to be used for other kinds of text output also.
|
||||||
|
|
||||||
# Directives
|
# Template directives
|
||||||
Each directive begins with an `@` sign.
|
Each template directive begins with an `@` sign.
|
||||||
Some directives begin contains a `{}` block, others only have `''` (string) parameters.
|
Some directives contain a `{}` block, others only have `''` (string) parameters.
|
||||||
More on the directives itself.
|
|
||||||
|
|
||||||
Newlines on the beginning and end are ignored in `{}` blocks,
|
Newlines on the beginning and end are ignored in `{}` blocks,
|
||||||
otherwise this (see [if](#if) for this syntax):
|
otherwise this (see [if](#if) for this syntax):
|
||||||
|
@ -12,17 +14,17 @@ otherwise this (see [if](#if) for this syntax):
|
||||||
<span>This is shown if bool_val is true</span>
|
<span>This is shown if bool_val is true</span>
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
would result in:
|
... would output:
|
||||||
```html
|
```html
|
||||||
|
|
||||||
<span>This is shown if bool_val is true</span>
|
<span>This is shown if bool_val is true</span>
|
||||||
|
|
||||||
```
|
```
|
||||||
which could result in unreadable output.
|
... which is less readable.
|
||||||
|
|
||||||
## if
|
## if
|
||||||
The if directive consists of three parts, the `@if` tag, the condition (same syntax like in V)
|
The if directive, consists of three parts, the `@if` tag, the condition (same syntax like in V)
|
||||||
and the `{}` block where you can write html which will be rendered if the condition is true:
|
and the `{}` block, where you can write html, which will be rendered if the condition is true:
|
||||||
```
|
```
|
||||||
@if <condition> {}
|
@if <condition> {}
|
||||||
```
|
```
|
||||||
|
@ -42,19 +44,20 @@ The first example would result in:
|
||||||
```html
|
```html
|
||||||
<span>This is shown if bool_val is true</span>
|
<span>This is shown if bool_val is true</span>
|
||||||
```
|
```
|
||||||
while the one-liner results in:
|
... while the one-liner results in:
|
||||||
```html
|
```html
|
||||||
<span>This is shown if bool_val is true</span>
|
<span>This is shown if bool_val is true</span>
|
||||||
```
|
```
|
||||||
|
|
||||||
## for
|
## for
|
||||||
The for directive consists of three parts, the `@for` tag, the condition (same syntax like in V)
|
The for directive consists of three parts, the `@for` tag,
|
||||||
and the `{}` block where you can write html which will be rendered for each loop:
|
the condition (same syntax like in V) and the `{}` block,
|
||||||
|
where you can write text, rendered for each iteration of the loop:
|
||||||
```
|
```
|
||||||
@for <condition> {}
|
@for <condition> {}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Example
|
### Example for @for
|
||||||
```html
|
```html
|
||||||
@for i, val in my_vals {
|
@for i, val in my_vals {
|
||||||
<span>$i - $val</span>
|
<span>$i - $val</span>
|
||||||
|
@ -72,7 +75,7 @@ The first example would result in:
|
||||||
<span>2 - "Third"</span>
|
<span>2 - "Third"</span>
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
while the one-liner results in:
|
... while the one-liner results in:
|
||||||
```html
|
```html
|
||||||
<span>0 - "First"</span>
|
<span>0 - "First"</span>
|
||||||
<span>1 - "Second"</span>
|
<span>1 - "Second"</span>
|
||||||
|
@ -92,8 +95,7 @@ The include directive is for including other html files (which will be processed
|
||||||
and consists of two parts, the `@include` tag and a following `'<path>'` string.
|
and consists of two parts, the `@include` tag and a following `'<path>'` string.
|
||||||
The path parameter is relative to the `/templates` directory in the corresponding project.
|
The path parameter is relative to the `/templates` directory in the corresponding project.
|
||||||
|
|
||||||
### Example
|
### Example for the folder structure of a project using templates:
|
||||||
Files
|
|
||||||
```
|
```
|
||||||
Project root
|
Project root
|
||||||
/templates
|
/templates
|
||||||
|
@ -117,11 +119,11 @@ where you can insert your src
|
||||||
@js '<url>'
|
@js '<url>'
|
||||||
```
|
```
|
||||||
|
|
||||||
### Example
|
### Example for the @js directive:
|
||||||
```html
|
```html
|
||||||
@js 'myscripts.js'
|
@js 'myscripts.js'
|
||||||
```
|
```
|
||||||
|
|
||||||
# Variables
|
# Variables
|
||||||
All variables which are declared before can be used through the `@{my_var}` syntax.
|
All variables, which are declared before the $tmpl can be used through the `@{my_var}` syntax.
|
||||||
It's also possible to use properties of structs here like `@{my_struct.prop}`.
|
It's also possible to use properties of structs here like `@{my_struct.prop}`.
|
||||||
|
|
Loading…
Reference in New Issue