module vbugreport import net.urllib import os const github_bug_issue_form_base_uri = 'https://github.com/vlang/v/issues/new?template=bug-report.yml' const github_issue_form_url_soft_limit = 7500 pub struct BugReport { description string reproduction string expected string current string solution string context string version string environment string } struct IssueFormField { name string label string value string } pub enum BugReportDeliveryMode { github_form local_report } pub struct BugReportDelivery { pub: mode BugReportDeliveryMode uri string local_report_path string local_report_body string } fn field_value_or_default(value string, default_value string) string { trimmed := value.trim_space() if trimmed.len == 0 { return default_value } return trimmed } fn fenced_block(language string, value string) string { content := field_value_or_default(value, 'N/A') return '```' + language + '\n' + content + '\n```' } fn quoted_command(file_path string, generated_file string, user_args string) string { mut compile_cmd := './vdbg' if user_args.len > 0 { compile_cmd += ' ${user_args}' } compile_cmd += ' ${os.quoted_path(file_path)}' return './v -g -o vdbg cmd/v && ${compile_cmd} && ${os.quoted_path(os.real_path(generated_file))}' } // new_bug_report builds the structured fields used by the GitHub bug report form. pub fn new_bug_report(file_path string, generated_file string, user_args string, expected_result string, version string, vdoctor_output string, file_content string, build_output string) BugReport { command := quoted_command(file_path, generated_file, user_args) source_name := os.file_name(file_path) return BugReport{ description: 'Running `${command}` on `${source_name}` produced a compiler error.' reproduction: '${fenced_block('sh', command)}\n\n${fenced_block('v', file_content)}' expected: field_value_or_default(expected_result, 'N/A') current: fenced_block('', build_output) solution: '_No response_' context: 'Generated by `v bug` from `${file_path}`.' version: field_value_or_default(version, 'N/A') environment: fenced_block('', vdoctor_output) } } // issue_form_fields must stay aligned with `.github/ISSUE_TEMPLATE/bug-report.yml`. fn issue_form_fields(report BugReport) []IssueFormField { return [ IssueFormField{ name: 'description' label: 'Describe the bug' value: report.description }, IssueFormField{ name: 'reproduction' label: 'Reproduction Steps' value: report.reproduction }, IssueFormField{ name: 'expected' label: 'Expected Behavior' value: report.expected }, IssueFormField{ name: 'current' label: 'Current Behavior' value: report.current }, IssueFormField{ name: 'solution' label: 'Possible Solution' value: report.solution }, IssueFormField{ name: 'context' label: 'Additional Information/Context' value: report.context }, IssueFormField{ name: 'version' label: 'V version' value: report.version }, IssueFormField{ name: 'environment' label: 'Environment details (OS name and version, etc.)' value: report.environment }, ] } fn github_issue_form_uri(report BugReport) string { mut query_params := []string{} for field in issue_form_fields(report) { query_params << '${field.name}=${urllib.query_escape(field.value)}' } return '${github_bug_issue_form_base_uri}&${query_params.join('&')}' } fn bug_report_markdown(report BugReport) string { mut lines := [ '# V Bug Report', '', 'Generated by `v bug`.', '', ] for field in issue_form_fields(report) { lines << '### ${field.label}' lines << '' lines << field.value lines << '' } return lines.join('\n') } fn bug_report_file_path(file_path string) string { base_name := os.file_name(file_path) stem := if base_name.contains('.') { base_name.all_before_last('.') } else { base_name } file_name := if stem.len == 0 { 'v-bug-report.md' } else { '${stem}.bug-report.md' } file_dir := os.dir(file_path) return if file_dir.len == 0 || file_dir == '.' { file_name } else { os.join_path(file_dir, file_name) } } // prepare_bug_report_delivery chooses between a prefilled issue-form URL and a local markdown fallback. pub fn prepare_bug_report_delivery(report BugReport, file_path string) BugReportDelivery { uri := github_issue_form_uri(report) if uri.len <= github_issue_form_url_soft_limit { return BugReportDelivery{ mode: .github_form uri: uri } } return BugReportDelivery{ mode: .local_report uri: github_bug_issue_form_base_uri local_report_path: bug_report_file_path(file_path) local_report_body: bug_report_markdown(report) } }