mirror of
https://github.com/tiennm99/thptqg.git
synced 2026-10-18 12:14:37 +00:00
refactor: split into web/crawler/go-parser and drop the 2017 archives
Move the frontend into web/, the repo's only npm workspace, and replace the JS crawler with a Go module covering both remaining datasets. The crawler writes to a .part file and renames on completion: writing straight to the destination left truncated files that the skip-if-present check would then skip forever. Remove the 2017-old and 2017-old2 datasets. They were successive publications of the same exam, kept side by side so the disagreement stayed inspectable; the current 2017 supersedes them and they remain in git history. Recover the 2016 crawler source from the Internet Archive's copy of the aggregator article, whose original host no longer resolves. All 119 filenames are verified against data/2016 in both directions, but no archive captured the spreadsheets themselves, so the host still serving them is unconfirmed and data/2016 remains the only confirmed copy. Filenames are load-bearing throughout: go-parser sorts inputs bytewise and inserts last-wins, so they decide which row survives a duplicate exam number.
This commit is contained in:
1 parent
8530df0154
commit
ceb694a747
163 files changed
+1317
-510
No files matched your search
+56
-20
@@ -2,32 +2,63 @@
|
||||
|
||||
From raw Excel files to a compressed SQLite file the browser can load.
|
||||
|
||||
One Rust binary (`go-parser/`) builds every dataset. What differs per dataset is
|
||||
One Go binary (`go-parser/`) builds every dataset. What differs per dataset is
|
||||
parse rules only — sheet strategy, column layout, validation guards — declared
|
||||
in `go-parser/configs/<id>.yml`. The table shape, the INSERT and the subject
|
||||
regexes are canonical and live in `go-parser/internal/schema/schema.go`.
|
||||
|
||||
## Sources
|
||||
|
||||
| id | Files | Reproducible | Origin |
|
||||
| id | Files | Origin | Host live? |
|
||||
| --- | --- | --- | --- |
|
||||
| `2016` | 4 `.xls` + 115 `.xlsx` | no | Bộ GD&ĐT, collected 2016 |
|
||||
| `2017` | 63 `.xls` | **yes** | baotintuc.vn CDN |
|
||||
| `2017-old` | 63 `.xlsx` | no | pre-refresh archive |
|
||||
| `2017-old2` | 54 `.xlsx` | no | corrected re-export |
|
||||
| `2016` | 4 `.xls` + 115 `.xlsx` | aggregator article, 119 exam clusters | unconfirmed |
|
||||
| `2017` | 63 `.xls` | baotintuc.vn CDN | **yes** |
|
||||
|
||||
Only `2017` can be re-fetched:
|
||||
Crawling lives in `crawler/`, a separate Go module. It is never part of the
|
||||
build — the source files are committed, so a crawl only refreshes them. Both
|
||||
runs are idempotent: files already present are skipped.
|
||||
|
||||
```bash
|
||||
node go-parser/scripts/crawl-baotintuc.js
|
||||
npm run crawl:2016 # crawler/internal/sources/source_2016.go
|
||||
npm run crawl:2017 # crawler/internal/sources/source_2017.go
|
||||
npm run crawl:2017 -- --list # show what would be downloaded, fetch nothing
|
||||
```
|
||||
|
||||
Idempotent — skips files already present, saves to `data/2017/`. Source article:
|
||||
**2017** comes from the article
|
||||
`https://baotintuc.vn/tuyen-sinh/tra-cuu-diem-thi-thpt-2017-cua-63-tinh-thanh-pho-tren-baotintucvn-20170706073512672.htm`
|
||||
and its CDN is still serving the files.
|
||||
|
||||
The other three were collected from Vietnamese news sites at the time and the
|
||||
publisher URLs were not recorded. **The files committed in git are the only
|
||||
copy.**
|
||||
**2016** comes from the aggregator article
|
||||
`cong-bo-diem-thi-thptqg-2016-toan-bo-120-cum-thi-da-co-diem.html`. The site
|
||||
that originally published it (`dtntbacgiang.edu.vn`) no longer resolves, so the
|
||||
link list was read from the Internet Archive's copy of the page. Two caveats
|
||||
that matter:
|
||||
|
||||
- **The list is verified; the host is not.** All 119 filenames match `data/2016/`
|
||||
exactly in both directions, which is asserted by a test. But the Archive
|
||||
captured only the article, not the spreadsheets, so the URLs themselves could
|
||||
not be confirmed by fetching. `baseURL` in `source_2016.go` points at a mirror
|
||||
of the same article that is still online; if it serves the files from a
|
||||
different directory, `uploadPath` is the one constant to change.
|
||||
- **`data/2016/` is still the only confirmed copy.** Do not delete it on the
|
||||
assumption that a crawl can restore it.
|
||||
|
||||
### Local filenames are load-bearing
|
||||
|
||||
`go-parser` sorts its input files and inserts with `INSERT OR REPLACE`, which is
|
||||
last-wins, so **filenames decide which row survives a duplicate exam number**. A
|
||||
re-crawl that names files differently can produce a database with the same row
|
||||
count and different content, which the row-count guard in `build-db.js` would
|
||||
not catch.
|
||||
|
||||
Each source therefore pins its local names, and
|
||||
`crawler/internal/sources/sources_test.go` checks every source against its
|
||||
committed `data/<id>/` in both directions — every name it would write exists,
|
||||
and every file present is accounted for.
|
||||
|
||||
2017 derives its names from the province (the CDN's own names are inconsistent:
|
||||
`Angiang.xls`, `1BaRiaVungTau.xls`, `23HaiPhong.xls`). 2016 keeps the
|
||||
server-assigned names verbatim, since it did not choose them.
|
||||
|
||||
## Source Excel shapes
|
||||
|
||||
@@ -41,7 +72,7 @@ The 2017 datasets share one layout:
|
||||
| 3 | DIEM_THI | concatenated per-subject scores, e.g. `"Toán: 6.80 Ngữ văn: 5.25 …"` |
|
||||
|
||||
2016 has **three** layouts across its 119 files, chosen per file at runtime via
|
||||
`format_detection = "thptqg2016"` in its config:
|
||||
`format_detection: thptqg2016` in its config:
|
||||
|
||||
| Format | Detected by | Notes |
|
||||
| --- | --- | --- |
|
||||
@@ -95,8 +126,6 @@ scores rather than false matches.
|
||||
| --- | --- | --- |
|
||||
| `2016` | all sheets | per-file format detection; header-token rows rejected |
|
||||
| `2017` | all sheets — Hà Nội and HCM overflow | none |
|
||||
| `2017-old` | first sheet only | reject non-numeric SBD (rejects a header leak in this export) |
|
||||
| `2017-old2` | all sheets — HCM overflows | reject non-numeric SBD; skip blank rows before counting |
|
||||
|
||||
### Overflow-sheet gotcha
|
||||
|
||||
@@ -111,8 +140,6 @@ silently drops 13,720 students** (Hanoi +7,275, HCM +6,445). That is what
|
||||
| --- | --- | --- | --- |
|
||||
| `2016` | 877,464 | 3 duplicate SBDs collapsed | **877,461** |
|
||||
| `2017` | 861,068 | 0 | **861,068** |
|
||||
| `2017-old` | 847,349 | 1 header leak | **847,348** |
|
||||
| `2017-old2` | 679,764 | 0 | **679,764** |
|
||||
|
||||
## Verifying a rebuild
|
||||
|
||||
@@ -143,11 +170,14 @@ cell of any input file reads differently.
|
||||
|
||||
```bash
|
||||
rm data/2017/*.xls
|
||||
node go-parser/scripts/crawl-baotintuc.js
|
||||
node go-parser/scripts/build-db.js 2017
|
||||
npm run crawl:2017
|
||||
npm run build:db 2017
|
||||
```
|
||||
|
||||
The row-count guard in `build:db` confirms the rebuild matches the expected total.
|
||||
The row-count guard in `build:db` confirms the rebuild matches the expected
|
||||
total. That guard checks the count only, so if the crawl was expected to change
|
||||
the data, compare content with `differential-parity.mjs` against a copy of the
|
||||
previous database rather than trusting the count.
|
||||
|
||||
## Removed scripts
|
||||
|
||||
@@ -157,3 +187,9 @@ repo was unified (a hardcoded Windows path in one, an undeclared
|
||||
`better-sqlite3` dependency in the other) and neither had any automated caller.
|
||||
The latter two are superseded by `differential-parity.mjs`, which compares more
|
||||
and cannot silently skip a dataset.
|
||||
|
||||
`crawl-baotintuc.js` was not dropped but rewritten in Go as
|
||||
`crawler/internal/sources/source_2017.go`, keeping the same link list and the same
|
||||
local filenames. The rewrite added a per-file timeout and made downloads land on
|
||||
a `.part` file first — writing straight to the destination meant an interrupted
|
||||
run left a truncated file that the skip check would then skip forever.
|
||||
+10
-12
@@ -1,7 +1,7 @@
|
||||
# Deployment Guide
|
||||
|
||||
Deploys to GitHub Pages via `.github/workflows/deploy-pages.yml`. Every push to
|
||||
`main` rebuilds all four datasets and redeploys the whole site.
|
||||
`main` rebuilds both datasets and redeploys the whole site.
|
||||
|
||||
One-time setup: **Settings → Pages → Source: GitHub Actions**.
|
||||
|
||||
@@ -9,9 +9,9 @@ One-time setup: **Settings → Pages → Source: GitHub Actions**.
|
||||
|
||||
1. Checkout, Go toolchain, Node 24, `npm ci`
|
||||
2. `npm run build:go` — one parser binary
|
||||
3. `npm run build:db` — builds and gzips all four databases into
|
||||
3. `npm run build:db` — builds and gzips both databases into
|
||||
`.build/public/db/`
|
||||
4. `npm run build:site` — one Vite build, then `scripts/assemble-site.js`
|
||||
4. `npm run build:site` — one Vite build, then `web/scripts/assemble-site.js`
|
||||
5. `actions/upload-pages-artifact` + `actions/deploy-pages`
|
||||
|
||||
The database build dominates the runtime: roughly 419 MB of Excel is parsed on
|
||||
@@ -23,20 +23,18 @@ every deploy.
|
||||
https://<user>.github.io/thptqg/
|
||||
https://<user>.github.io/thptqg/2016/
|
||||
https://<user>.github.io/thptqg/2017/
|
||||
https://<user>.github.io/thptqg/2017-old/
|
||||
https://<user>.github.io/thptqg/2017-old2/
|
||||
```
|
||||
|
||||
`/thptqg/2017/old/` and `/thptqg/2017/old2/` were the pre-flattening URLs. They
|
||||
are still served, and the router rewrites them to the flat form with the query
|
||||
string intact.
|
||||
`/thptqg/2017/old/` and `/thptqg/2017/old2/` were the pre-flattening URLs for
|
||||
the two removed 2017 archives. They are no longer served; like any unknown path
|
||||
they now render the hub via `404.html`.
|
||||
|
||||
## Local reproduction
|
||||
|
||||
```bash
|
||||
npm ci
|
||||
npm run build:go
|
||||
npm run build:db # all four; pass an id to build just one
|
||||
npm run build:db # both; pass an id to build just one
|
||||
npm run build:site # vite build + assemble into _site/
|
||||
npx serve _site
|
||||
```
|
||||
@@ -44,7 +42,7 @@ npx serve _site
|
||||
To rebuild a single dataset:
|
||||
|
||||
```bash
|
||||
node go-parser/scripts/build-db.js 2017-old
|
||||
npm run build:db 2017
|
||||
```
|
||||
|
||||
## Base path
|
||||
@@ -59,7 +57,7 @@ up as a blank page with 404s on `/assets/...`.
|
||||
2. Add `go-parser/configs/<id>.yml` with the parse rules — sheet mode, column
|
||||
indices, SBD validation, header tokens, blank-row stripping. No SQL: the
|
||||
schema is canonical and lives in `go-parser/internal/schema/schema.go`
|
||||
3. Add an entry to `DATASETS` in `src/datasets.js`
|
||||
3. Add an entry to `DATASETS` in `web/src/datasets.js`
|
||||
|
||||
Nothing else. The build script, the site assembly and the router all read that
|
||||
one list, and the frontend adapts to whichever columns the dataset populates.
|
||||
@@ -98,7 +96,7 @@ run rebuilds the older state. There is no data to migrate.
|
||||
| Symptom | Typical cause |
|
||||
| --- | --- |
|
||||
| Blank page, 404 on assets | `base` in `vite.config.js` does not match the repo name |
|
||||
| `Failed to fetch database: 404` | Dataset id in `src/datasets.js` does not match the file in `db/` |
|
||||
| `Failed to fetch database: 404` | Dataset id in `web/src/datasets.js` does not match the file in `db/` |
|
||||
| A route 404s | `assemble-site.js` did not run, or the id is missing from `DATASETS` |
|
||||
| WASM fails to load | `sql.js.org` unreachable — self-host `sql-wasm.wasm` and update `SQL_WASM_URL` in `use-sqlite.js` |
|
||||
| Deploy fails on assembly | An uncompressed database artefact reached the output; the error names the files |
|
||||
|
||||
+12
-11
@@ -4,7 +4,7 @@
|
||||
|
||||
A public lookup tool for Vietnam's National High School Graduation Exam scores,
|
||||
running entirely in the browser and hosted for free on GitHub Pages. Covers the
|
||||
2016 and 2017 exams — 1.7 million candidates across four datasets.
|
||||
2016 and 2017 exams — 1.7 million candidates across two datasets.
|
||||
|
||||
## Scope
|
||||
|
||||
@@ -21,7 +21,7 @@ running entirely in the browser and hosted for free on GitHub Pages. Covers the
|
||||
|
||||
## Constraints
|
||||
|
||||
- **Zero backend.** The full database (38–48 MB gzipped per dataset) is
|
||||
- **Zero backend.** The full database (44–48 MB gzipped per dataset) is
|
||||
downloaded to the browser and queried in-process.
|
||||
- **Read-only.** `INSERT`/`UPDATE`/`DELETE` are rejected, so nobody is misled
|
||||
into thinking edits persist. `sql.js` is in-memory anyway.
|
||||
@@ -36,16 +36,17 @@ running entirely in the browser and hosted for free on GitHub Pages. Covers the
|
||||
| --- | --- | --- | --- |
|
||||
| `2016` | 2016 | 877,461 | 119 files, three column layouts |
|
||||
| `2017` | 2017 | 861,068 | current generation, reproducible from source |
|
||||
| `2017-old` | 2017 | 847,348 | pre-refresh archive |
|
||||
| `2017-old2` | 2017 | 679,764 | corrected re-export |
|
||||
|
||||
The three 2017 datasets are kept side by side because they disagree, and the
|
||||
disagreement is itself informative. Only `2017` is re-fetchable; the rest exist
|
||||
solely as the copies committed here.
|
||||
Two further 2017 datasets (`2017-old`, `2017-old2`) were kept alongside these
|
||||
because the three publications disagreed. They have been removed; git history
|
||||
still has them.
|
||||
|
||||
Original 2016 aggregator link
|
||||
(<https://dtntbacgiang.edu.vn/tin-tuc/tin-tuc-su-kien/cong-bo-diem-thi-thptqg-2016-toan-bo-120-cum-thi-da-co-diem.html>)
|
||||
is no longer accessible, which is why the raw files are mirrored in `data/2016/`.
|
||||
Both datasets now have a crawler source. 2017 comes from the baotintuc.vn CDN,
|
||||
which is still live. 2016 comes from an aggregator article whose original host
|
||||
(`dtntbacgiang.edu.vn`) no longer resolves — its link list was recovered from
|
||||
the Internet Archive and is pointed at a mirror that is still online. See
|
||||
[data-pipeline](./data-pipeline.md#sources) for what that does and does not
|
||||
guarantee.
|
||||
|
||||
## History
|
||||
|
||||
@@ -53,7 +54,7 @@ Each year began as a standalone repository (`thptqg2016`, `thptqg2017`), merged
|
||||
here with full history. They initially kept separate frontends and separate
|
||||
copies of the same Rust parser, synchronised by hand. That duplication was
|
||||
removed: there is now one frontend, one parser, and one canonical schema, with
|
||||
per-dataset differences confined to four small config files and one registry
|
||||
per-dataset differences confined to one small config file and one registry
|
||||
entry each.
|
||||
|
||||
The unification also fixed a latent data-loss bug — neither year's parser
|
||||
|
||||
+30
-29
@@ -3,23 +3,24 @@
|
||||
Static site, no backend. The browser downloads a compressed SQLite file at boot
|
||||
and every query runs locally via `sql.js` (SQLite compiled to WebAssembly).
|
||||
|
||||
One frontend, one parser, one schema, four datasets.
|
||||
One frontend, one parser, one schema, two datasets.
|
||||
|
||||
## Data flow
|
||||
|
||||
```
|
||||
▲ crawler/ (Go — manual refresh only, never part of the build)
|
||||
data/<id>/*.xls(x)
|
||||
│
|
||||
▼ parser/ (Rust, one binary, one config per dataset)
|
||||
▼ go-parser/ (Go, one binary, one config per dataset)
|
||||
.build/public/db/<id>.db
|
||||
│
|
||||
▼ gzip -9 (no -k: the raw file does not survive)
|
||||
.build/public/db/<id>.db.gz
|
||||
│
|
||||
▼ vite build (publicDir = .build/public)
|
||||
dist/
|
||||
▼ vite build (root = web/, publicDir = .build/public)
|
||||
web/dist/
|
||||
│
|
||||
▼ scripts/assemble-site.js
|
||||
▼ web/scripts/assemble-site.js
|
||||
_site/ → GitHub Pages
|
||||
│
|
||||
▼ browser
|
||||
@@ -31,26 +32,28 @@ data/<id>/*.xls(x)
|
||||
One identifier ties the whole pipeline together:
|
||||
|
||||
```
|
||||
data/2017-old/ → go-parser/configs/2017-old.yml → db/2017-old.db.gz → /thptqg/2017-old/
|
||||
data/2017/ → go-parser/configs/2017.yml → db/2017.db.gz → /thptqg/2017/
|
||||
```
|
||||
|
||||
`src/datasets.js` declares the four ids once. The frontend, the database build
|
||||
(`go-parser/scripts/build-db.js`) and the site assembly all import that list, so
|
||||
adding a dataset means adding one entry and one config file.
|
||||
`web/src/datasets.js` declares the two ids once. The frontend, the database
|
||||
build (`go-parser/scripts/build-db.js`) and the site assembly all import that
|
||||
list, so adding a dataset means adding one entry and one config file.
|
||||
|
||||
That import crosses a package boundary — go-parser reaches into the web
|
||||
workspace for it. It stays there because the same entries also carry the UI's
|
||||
labels and SQL presets, and splitting them would mean two lists to keep in step.
|
||||
|
||||
| id | Exam | Rows | Source |
|
||||
| --- | --- | --- | --- |
|
||||
| `2016` | 2016 | 877,461 | Bộ GD&ĐT |
|
||||
| `2017` | 2017 | 861,068 | baotintuc.vn |
|
||||
| `2017-old` | 2017 | 847,348 | pre-refresh archive |
|
||||
| `2017-old2` | 2017 | 679,764 | corrected re-export |
|
||||
|
||||
## Canonical schema
|
||||
|
||||
Defined once in `go-parser/internal/schema/schema.go` — DDL, INSERT, column order and the 16
|
||||
subject regexes. The four TOML configs carry no SQL at all, only per-dataset
|
||||
parse rules. Config parsing uses `deny_unknown_fields`, so a leftover `[schema]`
|
||||
block fails loudly instead of looking effective while `schema.rs` drives the
|
||||
subject regexes. The two YAML configs carry no SQL at all, only per-dataset
|
||||
parse rules. Config parsing sets `KnownFields(true)`, so a leftover `schema:`
|
||||
block fails loudly instead of looking effective while `schema.go` drives the
|
||||
build.
|
||||
|
||||
```sql
|
||||
@@ -73,7 +76,7 @@ CREATE INDEX idx_ten_cum_thi ON student(ten_cum_thi) WHERE ten_cum_thi IS NOT N
|
||||
|
||||
Every dataset gets all 22 columns; ones it has no data for are NULL, costing
|
||||
about a byte per row. `khtn`, `khxh` and `gdcd` are empty on 2016;
|
||||
`ten_cum_thi` and `gioi_tinh` are empty on the 2017 datasets.
|
||||
`ten_cum_thi` and `gioi_tinh` are empty on 2017.
|
||||
|
||||
`idx_ten_cum_thi` is partial, so it holds zero entries where the column is
|
||||
always NULL.
|
||||
@@ -86,15 +89,13 @@ URLs are flat, one segment per dataset, and the segment is the id:
|
||||
/thptqg/ hub
|
||||
/thptqg/2016/
|
||||
/thptqg/2017/
|
||||
/thptqg/2017-old/
|
||||
/thptqg/2017-old2/
|
||||
```
|
||||
|
||||
`src/router.js` is an exact match on that segment. The nested form used before
|
||||
(`/thptqg/2017/old/`) would have needed longest-prefix matching, since it also
|
||||
starts with `/thptqg/2017/`. Those two legacy URLs still resolve: the router
|
||||
rewrites them to the flat equivalent with `history.replaceState`, preserving the
|
||||
query string so `?q=` deep links survive.
|
||||
`web/src/router.js` is an exact match on that segment. The nested form used
|
||||
before (`/thptqg/2017/old/`) would have needed longest-prefix matching, since it
|
||||
also starts with `/thptqg/2017/`. Both of those URLs addressed the two removed
|
||||
2017 archives, so the rewrite that kept them working has gone with them; they
|
||||
fall through to the hub like any other unknown path.
|
||||
|
||||
A single Vite build emits one `index.html`. Because `base` is absolute
|
||||
(`/thptqg/`), that file references `/thptqg/assets/...` regardless of the
|
||||
@@ -115,11 +116,11 @@ No component contains a per-dataset conditional. Two mechanisms do the work:
|
||||
self-exclude wherever those languages were not sat.
|
||||
|
||||
Anything genuinely per-dataset — title, source, database size, search examples,
|
||||
SQL presets — lives in `src/datasets.js`.
|
||||
SQL presets — lives in `web/src/datasets.js`.
|
||||
|
||||
## Exam ID formats
|
||||
|
||||
`src/lib/query-mode.js` decides whether a query is an exam ID or a name, and is
|
||||
`web/src/lib/query-mode.js` decides whether a query is an exam ID or a name, and is
|
||||
shared by `App.jsx` and `search-form.jsx` (they previously held separate copies
|
||||
and had drifted apart on exactly this rule).
|
||||
|
||||
@@ -135,7 +136,7 @@ candidates (70.3%). Letter prefixes are upper-cased before lookup, so
|
||||
|
||||
## Score tiers
|
||||
|
||||
Six-level ladder in `scoreTier()` (`src/lib/admission-blocks.js`), paired with a
|
||||
Six-level ladder in `scoreTier()` (`web/src/lib/admission-blocks.js`), paired with a
|
||||
symbol so meaning is never colour-only.
|
||||
|
||||
| Tier | Range | Vietnamese |
|
||||
@@ -150,7 +151,7 @@ symbol so meaning is never colour-only.
|
||||
## Admission blocks
|
||||
|
||||
Vietnamese universities admit on three-subject combinations (khối thi).
|
||||
`src/lib/admission-blocks.js` lists the blocks computable from this schema
|
||||
`web/src/lib/admission-blocks.js` lists the blocks computable from this schema
|
||||
(A00–A11, B00–B08, C00–C20, D01–D15, plus D05/D06 for German and Japanese).
|
||||
`computeBlocks(student)` returns those where all three scores exist, sorted by
|
||||
total descending.
|
||||
@@ -165,11 +166,11 @@ total descending.
|
||||
| Diacritics search | Pre-computed `ho_ten_ascii` | `LOWER(REPLACE(...))` at query time defeats the index |
|
||||
| SQL safety | Leading-keyword allowlist | `sql.js` is in-memory so writes cannot persist; the allowlist prevents confusion |
|
||||
| Row caps | 100 (lookup), 1000 (SQL) | Keeps DOM render sizes reasonable |
|
||||
| Routing | Hand-rolled, ~20 lines | Five static routes do not justify a router dependency |
|
||||
| Routing | Hand-rolled, ~15 lines | Three static routes do not justify a router dependency |
|
||||
|
||||
## Risks and limitations
|
||||
|
||||
- **Database size.** 38–48 MB gzipped per dataset; slow links wait, mitigated by
|
||||
- **Database size.** 44–48 MB gzipped per dataset; slow links wait, mitigated by
|
||||
a progress bar.
|
||||
- **Browser memory.** The full database lives in RAM; older mobile devices may
|
||||
run out.
|
||||
@@ -177,4 +178,4 @@ total descending.
|
||||
load. Self-hosting `sql-wasm.wasm` and updating `SQL_WASM_URL` in
|
||||
`use-sqlite.js` is the fix.
|
||||
- **Excel format drift.** A new source file with an unseen header layout needs a
|
||||
new branch in `format_detect_2016.rs` or a new config.
|
||||
new branch in `go-parser/internal/ingest/detect2016.go` or a new config.
|
||||
Reference in new issue
Block a user