Skip to content

Commit a932b01

Browse files
committed
tools: add support for v quest
1 parent a50f8d5 commit a932b01

3 files changed

Lines changed: 153 additions & 0 deletions

File tree

‎cmd/tools/vquest.v‎

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
module main
2+
3+
import os
4+
import cli
5+
import net.http
6+
import net.urllib
7+
import json
8+
import rand
9+
import term
10+
11+
const search_endpoint = 'https://api.github.com/search/issues'
12+
const search_query = 'repo:vlang/v is:issue is:open -label:"Status: Confirmed"'
13+
const per_page = 100
14+
const max_search_results = 1000
15+
16+
struct SearchResponse {
17+
total_count int
18+
items []Issue
19+
}
20+
21+
struct Issue {
22+
html_url string
23+
}
24+
25+
fn main() {
26+
// the 0th arg is /path/to/vquest, the 1st is `quest`; the args after that are the subcommands
27+
mut args := []string{}
28+
args << os.args[0]
29+
args << os.args#[2..]
30+
mut app := cli.Command{
31+
name: 'quest'
32+
description: 'A tool to help make V better for everyone, by spending some time each day, on random tasks/missions like:\n * documenting public APIs\n * issue confirmation reviewing and triage\n * testing'
33+
execute: cli.print_help_for_command
34+
defaults: struct {
35+
man: false
36+
}
37+
commands: [
38+
cli.Command{
39+
name: 'document'
40+
description: 'Print a random missing doc entry from the V standard library.'
41+
execute: run_document
42+
},
43+
cli.Command{
44+
name: 'confirm'
45+
description: 'Open a random unconfirmed vlang/v issue in your browser.'
46+
flags: [
47+
cli.Flag{
48+
flag: .bool
49+
name: 'print-only'
50+
abbrev: 'p'
51+
description: 'Print the issue URL without opening a browser.'
52+
},
53+
]
54+
execute: run_confirm
55+
},
56+
]
57+
}
58+
app.setup()
59+
if args.len <= 1 {
60+
if rcmd := rand.element(app.commands) {
61+
rcmd.execute(rcmd)!
62+
return
63+
}
64+
}
65+
app.parse(args)
66+
}
67+
68+
fn run_confirm(cmd cli.Command) ! {
69+
print_only := cmd.flags.get_bool('print-only') or { false }
70+
total := fetch_total_count()!
71+
max_pages := total_to_max_pages(total)
72+
if max_pages == 0 {
73+
return error('no unconfirmed issues found')
74+
}
75+
page := (rand.intn(max_pages) or { 0 }) + 1
76+
eprintln(term.colorize(term.gray, 'Found: ${total} still unconfirmed issues. Fetching issue from page: ${page} ...'))
77+
issue := fetch_issue_from_page(page)!
78+
if print_only {
79+
println(issue.html_url)
80+
return
81+
}
82+
os.open_uri(issue.html_url)!
83+
println(term.colorize(term.green, 'Help us by confirming and triaging this issue:'))
84+
println(issue.html_url)
85+
}
86+
87+
fn run_document(cmd cli.Command) ! {
88+
res := os.execute('v missdoc --exclude vlib/v --exclude /linux_bare/ --exclude /wasm_bare/ @vlib')
89+
if res.exit_code != 0 {
90+
return error('v missdoc failed: ${res.output}')
91+
}
92+
lines := res.output.split_into_lines().filter(it.trim_space() != '')
93+
if lines.len == 0 {
94+
return error('no missing doc entries found')
95+
}
96+
idx := rand.intn(lines.len) or { 0 }
97+
eprintln(term.colorize(term.green, 'Help us document this public API:'))
98+
println(term.colorize(term.bold, lines[idx]))
99+
}
100+
101+
fn fetch_total_count() !int {
102+
url := build_search_url(1, 1)
103+
body := api_get(url)!
104+
resp := json.decode(SearchResponse, body)!
105+
return resp.total_count
106+
}
107+
108+
fn fetch_issue_from_page(page int) !Issue {
109+
url := build_search_url(page, per_page)
110+
body := api_get(url)!
111+
resp := json.decode(SearchResponse, body)!
112+
if resp.items.len == 0 {
113+
return error('no issues returned for page ${page}')
114+
}
115+
idx := rand.intn(resp.items.len) or { 0 }
116+
return resp.items[idx]
117+
}
118+
119+
fn build_search_url(page int, per_page int) string {
120+
mut values := urllib.new_values()
121+
values.add('q', search_query)
122+
values.add('per_page', per_page.str())
123+
values.add('page', page.str())
124+
return '${search_endpoint}?${values.encode()}'
125+
}
126+
127+
fn api_get(url string) !string {
128+
resp := http.fetch(
129+
url: url
130+
method: .get
131+
header: http.new_header_from_map({
132+
http.CommonHeader.accept: 'application/vnd.github+json'
133+
http.CommonHeader.user_agent: 'v quest'
134+
})
135+
)!
136+
if resp.status_code != 200 {
137+
return error('GitHub API error ${resp.status_code}: ${resp.body}')
138+
}
139+
return resp.body
140+
}
141+
142+
fn total_to_max_pages(total int) int {
143+
if total <= 0 {
144+
return 0
145+
}
146+
max_pages := (total + per_page - 1) / per_page
147+
limit := max_search_results / per_page
148+
return if max_pages < limit { max_pages } else { limit }
149+
}

‎cmd/v/v.v‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const external_tools = [
3434
'gret',
3535
'ls',
3636
'missdoc',
37+
'quest',
3738
'reduce',
3839
'repl',
3940
'repeat',

‎vlib/v/help/default.txt‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ V supports the following commands:
5555
example: `v watch run hello.v`
5656
where Find and print the location of current project
5757
declarations.
58+
quest Help us make V better, by doing a small randomly
59+
selected task, like triaging an issue, improving
60+
the documentation, testing, etc.
5861

5962
* Installation Management Utilities:
6063
symlink Create a symbolic link for V.

0 commit comments

Comments
 (0)