sslabdata 3.0.0__tar.gz → 3.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. {sslabdata-3.0.0/sslabdata.egg-info → sslabdata-3.1.0}/PKG-INFO +45 -3
  2. {sslabdata-3.0.0 → sslabdata-3.1.0}/README.md +44 -2
  3. {sslabdata-3.0.0 → sslabdata-3.1.0}/SPEC.md +72 -16
  4. {sslabdata-3.0.0 → sslabdata-3.1.0}/pyproject.toml +8 -5
  5. sslabdata-3.1.0/schema/input/v1/collaborators.schema.json +24 -0
  6. sslabdata-3.1.0/schema/input/v1/lab.schema.json +71 -0
  7. sslabdata-3.1.0/schema/input/v1/people.schema.json +47 -0
  8. sslabdata-3.1.0/schema/input/v1/projects.schema.json +34 -0
  9. {sslabdata-3.0.0 → sslabdata-3.1.0}/sslabdata/__init__.py +1 -1
  10. {sslabdata-3.0.0 → sslabdata-3.1.0}/sslabdata/assembler.py +3 -1
  11. {sslabdata-3.0.0 → sslabdata-3.1.0}/sslabdata/cli.py +139 -2
  12. {sslabdata-3.0.0 → sslabdata-3.1.0}/sslabdata/config.py +12 -0
  13. {sslabdata-3.0.0 → sslabdata-3.1.0}/sslabdata/diagnostics.py +6 -0
  14. {sslabdata-3.0.0 → sslabdata-3.1.0}/sslabdata/parsers/bibtex.py +178 -29
  15. sslabdata-3.1.0/sslabdata/templates/init/bib/publications.bib +11 -0
  16. sslabdata-3.1.0/sslabdata/templates/init/collaborators.yaml +8 -0
  17. sslabdata-3.1.0/sslabdata/templates/init/lab.yaml +28 -0
  18. sslabdata-3.1.0/sslabdata/templates/init/people.yaml +13 -0
  19. sslabdata-3.1.0/sslabdata/templates/init/projects.yaml +10 -0
  20. {sslabdata-3.0.0 → sslabdata-3.1.0/sslabdata.egg-info}/PKG-INFO +45 -3
  21. {sslabdata-3.0.0 → sslabdata-3.1.0}/sslabdata.egg-info/SOURCES.txt +10 -1
  22. {sslabdata-3.0.0 → sslabdata-3.1.0}/LICENSE +0 -0
  23. {sslabdata-3.0.0 → sslabdata-3.1.0}/MANIFEST.in +0 -0
  24. {sslabdata-3.0.0 → sslabdata-3.1.0}/schema/__init__.py +0 -0
  25. {sslabdata-3.0.0 → sslabdata-3.1.0}/schema/v3/output.schema.json +0 -0
  26. {sslabdata-3.0.0 → sslabdata-3.1.0}/schema/v4/output.schema.json +0 -0
  27. {sslabdata-3.0.0 → sslabdata-3.1.0}/schema/v5/output.schema.json +0 -0
  28. {sslabdata-3.0.0 → sslabdata-3.1.0}/setup.cfg +0 -0
  29. {sslabdata-3.0.0 → sslabdata-3.1.0}/sslabdata/exporters.py +0 -0
  30. {sslabdata-3.0.0 → sslabdata-3.1.0}/sslabdata/loaders.py +0 -0
  31. {sslabdata-3.0.0 → sslabdata-3.1.0}/sslabdata/models.py +0 -0
  32. {sslabdata-3.0.0 → sslabdata-3.1.0}/sslabdata/parsers/__init__.py +0 -0
  33. {sslabdata-3.0.0 → sslabdata-3.1.0}/sslabdata/parsers/latex.py +0 -0
  34. {sslabdata-3.0.0 → sslabdata-3.1.0}/sslabdata/resolver.py +0 -0
  35. {sslabdata-3.0.0 → sslabdata-3.1.0}/sslabdata.egg-info/dependency_links.txt +0 -0
  36. {sslabdata-3.0.0 → sslabdata-3.1.0}/sslabdata.egg-info/entry_points.txt +0 -0
  37. {sslabdata-3.0.0 → sslabdata-3.1.0}/sslabdata.egg-info/requires.txt +0 -0
  38. {sslabdata-3.0.0 → sslabdata-3.1.0}/sslabdata.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sslabdata
3
- Version: 3.0.0
3
+ Version: 3.1.0
4
4
  Summary: Renderer-agnostic academic lab data assembler: BibTeX + YAML → structured data
5
5
  Author: Siddhartha Srinivasa
6
6
  License-Expression: MIT
@@ -91,6 +91,21 @@ pytest
91
91
 
92
92
  The `test` extra installs `pytest` and `jsonschema`, which the tests need.
93
93
 
94
+ ## Start a new lab
95
+
96
+ ```bash
97
+ sslabdata init mylab
98
+ cd mylab && sslabdata --config lab.yaml --validate --strict
99
+ ```
100
+
101
+ `init` writes `lab.yaml`, `bib/publications.bib`, `people.yaml`,
102
+ `projects.yaml` and `collaborators.yaml` into `mylab/` (the current directory
103
+ if you name none), each with one fictional record and a comment on each
104
+ field. Replace them with your own. It never overwrites a file that is already
105
+ there and names each one it refuses; `--force` overwrites those files and
106
+ nothing else. The paths in `lab.yaml` are relative to the directory you run
107
+ sslabdata from, so run it from `mylab/`.
108
+
94
109
  ## Write `lab.yaml`
95
110
 
96
111
  ```yaml
@@ -107,7 +122,7 @@ bib_files:
107
122
  - name: "conference.bib"
108
123
  category: "Conference Papers"
109
124
 
110
- pdf_base_url: "https://mylab.example.org/pdfs"
125
+ pdf_base_url: "https://mylab.example.org/pdfs" # optional; leave out to guess no PDF links
111
126
  people_file: "data/people.yaml" # optional
112
127
  projects_file: "data/projects.yaml" # optional
113
128
  collaborators_file: "data/collaborators.yaml" # optional
@@ -260,6 +275,30 @@ reported under `RESOLVE-COLLABORATOR-ALIAS-IS-MEMBER` and left to the member.
260
275
  person's `photo`, and is `null` when absent. It is carried as plain text:
261
276
  deciding which URLs are safe to render is the renderer's job.
262
277
 
278
+ ### Checking inputs in an editor
279
+
280
+ Each input file has a JSON Schema in
281
+ [`schema/input/v1/`](https://github.com/siddhss5/sslabdata/tree/main/schema/input/v1):
282
+ `lab.schema.json`, `people.schema.json`, `projects.schema.json` and
283
+ `collaborators.schema.json`. The wheel installs them under
284
+ `sslabdata/schema/input/v1/`. An editor can use them to check and complete
285
+ the files as you write; `--validate` stays the check, and also reports what
286
+ a schema cannot see, such as a repeated id or a missing file. With the YAML
287
+ language server (the VS Code YAML extension, among others), name the schema
288
+ in a comment at the top of the file:
289
+
290
+ ```yaml
291
+ # yaml-language-server: $schema=https://raw.githubusercontent.com/siddhss5/sslabdata/input-schema-v1/schema/input/v1/people.schema.json
292
+ - id: "aadams"
293
+ name: "Alice Adams"
294
+ role: "professor"
295
+ ```
296
+
297
+ The URL is the schema's `$id`, served from the `input-schema-v1` tag, which is
298
+ never moved ([`SPEC.md` §6](https://github.com/siddhss5/sslabdata/blob/main/SPEC.md#6-version-policy)).
299
+ Use `lab.schema.json`, `projects.schema.json` or `collaborators.schema.json`
300
+ in the same way for the other files.
301
+
263
302
  ## How author matching works
264
303
 
265
304
  sslabdata matches the structured parts of each BibTeX author name (given, von,
@@ -381,7 +420,10 @@ Trusted Publishing; no token is stored anywhere. The version is written in
381
420
  2. **Release.** Set both versions to `3.0.0`, date the CHANGELOG heading,
382
421
  merge, and push the tag `v3.0.0`. The PyPI job waits in the `release`
383
422
  environment for a reviewer's approval, then publishes the files the run
384
- checked and creates the GitHub Release with them.
423
+ checked and creates the GitHub Release with them. A release that ships a
424
+ new schema version also pushes that version's tag, `schema-vN` or
425
+ `input-schema-vN`, at the same commit, since the schema's `$id` points
426
+ there.
385
427
  3. **Approve** from the Actions page, or from the command line:
386
428
 
387
429
  ```bash
@@ -54,6 +54,21 @@ pytest
54
54
 
55
55
  The `test` extra installs `pytest` and `jsonschema`, which the tests need.
56
56
 
57
+ ## Start a new lab
58
+
59
+ ```bash
60
+ sslabdata init mylab
61
+ cd mylab && sslabdata --config lab.yaml --validate --strict
62
+ ```
63
+
64
+ `init` writes `lab.yaml`, `bib/publications.bib`, `people.yaml`,
65
+ `projects.yaml` and `collaborators.yaml` into `mylab/` (the current directory
66
+ if you name none), each with one fictional record and a comment on each
67
+ field. Replace them with your own. It never overwrites a file that is already
68
+ there and names each one it refuses; `--force` overwrites those files and
69
+ nothing else. The paths in `lab.yaml` are relative to the directory you run
70
+ sslabdata from, so run it from `mylab/`.
71
+
57
72
  ## Write `lab.yaml`
58
73
 
59
74
  ```yaml
@@ -70,7 +85,7 @@ bib_files:
70
85
  - name: "conference.bib"
71
86
  category: "Conference Papers"
72
87
 
73
- pdf_base_url: "https://mylab.example.org/pdfs"
88
+ pdf_base_url: "https://mylab.example.org/pdfs" # optional; leave out to guess no PDF links
74
89
  people_file: "data/people.yaml" # optional
75
90
  projects_file: "data/projects.yaml" # optional
76
91
  collaborators_file: "data/collaborators.yaml" # optional
@@ -223,6 +238,30 @@ reported under `RESOLVE-COLLABORATOR-ALIAS-IS-MEMBER` and left to the member.
223
238
  person's `photo`, and is `null` when absent. It is carried as plain text:
224
239
  deciding which URLs are safe to render is the renderer's job.
225
240
 
241
+ ### Checking inputs in an editor
242
+
243
+ Each input file has a JSON Schema in
244
+ [`schema/input/v1/`](https://github.com/siddhss5/sslabdata/tree/main/schema/input/v1):
245
+ `lab.schema.json`, `people.schema.json`, `projects.schema.json` and
246
+ `collaborators.schema.json`. The wheel installs them under
247
+ `sslabdata/schema/input/v1/`. An editor can use them to check and complete
248
+ the files as you write; `--validate` stays the check, and also reports what
249
+ a schema cannot see, such as a repeated id or a missing file. With the YAML
250
+ language server (the VS Code YAML extension, among others), name the schema
251
+ in a comment at the top of the file:
252
+
253
+ ```yaml
254
+ # yaml-language-server: $schema=https://raw.githubusercontent.com/siddhss5/sslabdata/input-schema-v1/schema/input/v1/people.schema.json
255
+ - id: "aadams"
256
+ name: "Alice Adams"
257
+ role: "professor"
258
+ ```
259
+
260
+ The URL is the schema's `$id`, served from the `input-schema-v1` tag, which is
261
+ never moved ([`SPEC.md` §6](https://github.com/siddhss5/sslabdata/blob/main/SPEC.md#6-version-policy)).
262
+ Use `lab.schema.json`, `projects.schema.json` or `collaborators.schema.json`
263
+ in the same way for the other files.
264
+
226
265
  ## How author matching works
227
266
 
228
267
  sslabdata matches the structured parts of each BibTeX author name (given, von,
@@ -344,7 +383,10 @@ Trusted Publishing; no token is stored anywhere. The version is written in
344
383
  2. **Release.** Set both versions to `3.0.0`, date the CHANGELOG heading,
345
384
  merge, and push the tag `v3.0.0`. The PyPI job waits in the `release`
346
385
  environment for a reviewer's approval, then publishes the files the run
347
- checked and creates the GitHub Release with them.
386
+ checked and creates the GitHub Release with them. A release that ships a
387
+ new schema version also pushes that version's tag, `schema-vN` or
388
+ `input-schema-vN`, at the same commit, since the schema's `$id` points
389
+ there.
348
390
  3. **Approve** from the Actions page, or from the command line:
349
391
 
350
392
  ```bash
@@ -29,8 +29,8 @@ Pointer into `schema/v5/output.schema.json` such as `/$defs/person/required`, or
29
29
  a `tests/COVERAGE.md` row key such as `config.people_file.missing`. A bare
30
30
  statement is cited by its enclosing function.
31
31
 
32
- Nothing yet checks that these references resolve; #63 proposes the test that
33
- would.
32
+ `tests/test_spec_references.py` checks that every one of these references
33
+ resolves; its docstring says which spans count as references.
34
34
 
35
35
  ---
36
36
 
@@ -72,6 +72,35 @@ Flags, as `sslabdata.cli.main()` defines them:
72
72
  | `--unresolved` | List author names that matched no person, then exit. A name left ambiguous is one of them. |
73
73
  | `--strict` | Combines with any mode. Every coded diagnostic is an error except those the class table below marks as never an error: a redefined `@string` macro, and anything about an author who matched no lab member. Any error exits `1`, and an export writes nothing. Without it, the exit codes below are unchanged. |
74
74
 
75
+ **`sslabdata init [DIR] [--force]`** is the one subcommand, recognised only as
76
+ the first argument (`sslabdata.cli.main()`). The flag form takes no positional
77
+ argument, so no command line that form accepts begins with `init`, and every
78
+ such command line behaves as it did before `init` existed. `init` writes a
79
+ minimal starting point that passes `--validate --strict` into `DIR` (default:
80
+ the current directory), creating it if it is missing: `lab.yaml`,
81
+ `bib/publications.bib`, `people.yaml`, `projects.yaml` and
82
+ `collaborators.yaml`, each holding one fictional record and copied unchanged
83
+ from the package's data (`sslabdata.cli.INIT_FILES`). Nothing is fetched.
84
+ The paths in the `lab.yaml` it writes are relative to `DIR`, so the next
85
+ command, which it prints last on standard output after a `Wrote <path>` line
86
+ per file, runs from there. Every problem is found before any file is
87
+ written, so a refused run writes nothing:
88
+
89
+ - a file already at one of those paths is never overwritten without
90
+ `--force`, and each one is named (`INIT-FILE-EXISTS`); `--force`
91
+ overwrites those files and touches nothing else, replacing each
92
+ atomically, so a failed write leaves the old file as it was. A symlink is
93
+ replaced itself, never what it points to;
94
+ - `DIR` or `DIR/bib` that is not a directory, or a path it writes that is a
95
+ directory or is not a regular file, is refused even with `--force`
96
+ (`INIT-PATH-WRONG-KIND`);
97
+ - a `DIR/bib` that resolves outside `DIR` through a symlink is refused
98
+ (`INIT-PATH-OUTSIDE-DIR`).
99
+
100
+ A failure to create `DIR` or write a file is `INIT-WRITE-FAILED`. Every
101
+ `INIT-` diagnostic is unprefixed on standard error and exits `1`; a usage
102
+ error exits `2`.
103
+
75
104
  **At least one** of `--output`, `--validate` or `--unresolved` is required —
76
105
  not exactly one. `sslabdata.cli.main()` rejects only the case where all three
77
106
  are absent, so combinations are accepted and resolved by **precedence**:
@@ -84,9 +113,9 @@ Exit codes, as `sslabdata.cli.main()` returns them:
84
113
 
85
114
  | Code | Meaning |
86
115
  |---|---|
87
- | `0` | Success. `--output` wrote the file; `--validate` found no errors; `--unresolved` reported. |
88
- | `1` | Error. Configuration file missing, or configuration failed to load — unreadable, a `bib_files[].name` that is absolute or leaves `bib_dir`, a `lab.yaml` of the wrong shape, or a value under `lab` that has no one JSON form; a path the configuration names is not there, or is not a file (not a directory, for `bib_dir`); a people file cannot be read as records; an entry carries `crossref`; `--validate` found unknown project ids or duplicate citation keys, person ids or project ids; `--output` could not be written; or, under `--strict`, any coded diagnostic that the class table does not keep as a warning. The *Diagnostic codes* table below gives the class of each. |
89
- | `2` | Usage error from the argument parser: a missing or unrecognised flag, or none of `--output` / `--validate` / `--unresolved`. |
116
+ | `0` | Success. `--output` wrote the file; `--validate` found no errors; `--unresolved` reported; `init` wrote every file. |
117
+ | `1` | Error. Configuration file missing, or configuration failed to load — unreadable, a `bib_files[].name` that is absolute or leaves `bib_dir`, a `lab.yaml` of the wrong shape, or a value under `lab` that has no one JSON form; a path the configuration names is not there, or is not a file (not a directory, for `bib_dir`); a people file cannot be read as records; an entry carries `crossref`; `--validate` found unknown project ids or duplicate citation keys, person ids or project ids; `--output` could not be written; `init` refused or could not write a file; or, under `--strict`, any coded diagnostic that the class table does not keep as a warning. The *Diagnostic codes* table below gives the class of each. |
118
+ | `2` | Usage error from the argument parser: a missing or unrecognised flag, or none of `--output` / `--validate` / `--unresolved`; for `init`, an unrecognised flag or more than one `DIR`. |
90
119
 
91
120
  **Streams and message shapes.** Ordinary reporting goes to **standard
92
121
  output**: the counts and unresolved names of
@@ -170,9 +199,9 @@ without depending on English wording. Codes obey three rules:
170
199
  | Class | `--validate` | Every other mode | Codes |
171
200
  |---|---|---|---|
172
201
  | **Fatal at load** | `Error loading configuration: <CODE> …` (`Error: <CODE> …` for `CONFIG-NOT-FOUND`) on standard error; exits `1` before anything is compiled, so there is no report. | The same. | `CONFIG-BIB-FILE-ABSOLUTE`, `CONFIG-BIB-FILE-OUTSIDE-BIB-DIR`, `CONFIG-NOT-A-MAPPING`, `CONFIG-KEY-MISSING`, `CONFIG-TYPE-INVALID`, `CONFIG-VALUE-NOT-JSON`, `CONFIG-KEY-REPEATED`, `CONFIG-NOT-FOUND`, `CONFIG-UNREADABLE` |
173
- | **Fatal** | Listed under `Bibliography errors` and counted; exits `1`. | Written to standard error unprefixed; exits `1`, and `--output` writes nothing. | `BIB-CROSSREF-UNSUPPORTED`, `BIB-ENCODING-INVALID`, `CONFIG-FILE-NOT-FOUND`, `CONFIG-PATH-WRONG-KIND`, `PEOPLE-YAML-INVALID`, `PEOPLE-NOT-A-LIST`, `PEOPLE-FIELD-MISSING`, `PROJECTS-YAML-INVALID`, `PROJECTS-NOT-A-LIST`, `PROJECTS-FIELD-MISSING`, `COLLABORATORS-YAML-INVALID`, `COLLABORATORS-NOT-A-LIST`, `COLLABORATORS-FIELD-MISSING`, `RECORD-KEY-REPEATED`, `OUTPUT-WRITE-FAILED` |
202
+ | **Fatal** | Listed under `Bibliography errors` and counted; exits `1`. | Written to standard error unprefixed; exits `1`, and `--output` writes nothing. | `BIB-CROSSREF-UNSUPPORTED`, `BIB-ENCODING-INVALID`, `CONFIG-FILE-NOT-FOUND`, `CONFIG-PATH-WRONG-KIND`, `PEOPLE-YAML-INVALID`, `PEOPLE-NOT-A-LIST`, `PEOPLE-FIELD-MISSING`, `PROJECTS-YAML-INVALID`, `PROJECTS-NOT-A-LIST`, `PROJECTS-FIELD-MISSING`, `COLLABORATORS-YAML-INVALID`, `COLLABORATORS-NOT-A-LIST`, `COLLABORATORS-FIELD-MISSING`, `RECORD-KEY-REPEATED`, `OUTPUT-WRITE-FAILED`, `INIT-FILE-EXISTS`, `INIT-PATH-WRONG-KIND`, `INIT-PATH-OUTSIDE-DIR`, `INIT-WRITE-FAILED` |
174
203
  | **Validation error** | Listed under `Bibliography errors` and counted; exits `1`. | Prefixed `Warning: ` on standard error; the run continues and exits `0`. | `BIB-DUPLICATE-KEY`, `RESOLVE-PROJECT-UNKNOWN`, `PEOPLE-ID-DUPLICATE`, `PROJECTS-ID-DUPLICATE` |
175
- | **Warning** | Listed under `Warnings`; not counted, and does not change the exit code. | Prefixed `Warning: ` on standard error; the run continues. | `BIB-YEAR-MISSING`, `BIB-YEAR-INVALID`, `BIB-DOI-INVALID`, `BIB-OTHERS-NOT-LAST`, `BIB-STRING-UNDEFINED`, `BIB-SYNTAX-ERROR`, `BIB-BRACE-MISMATCH`, `BIB-VENUE-MISSING`, `BIB-ENTRY-TYPE-UNSUPPORTED`, `LATEX-COMMAND-UNKNOWN`, `ID-GROUPING-SPANS-SPELLINGS`, `ID-GROUPING-INITIALS-AMBIGUOUS`, `RESOLVE-AMBIGUOUS-NAME`, `RESOLVE-SUGGESTION`, `RESOLVE-COLLABORATOR-ALIAS-IS-MEMBER`, `PEOPLE-ALIAS-AMBIGUOUS`, `PEOPLE-ROLE-INVALID`, `PEOPLE-STATUS-INVALID`, `PROJECTS-STATUS-INVALID`, `CONFIG-LAB-NAME-MISSING`, `CONFIG-KEY-UNKNOWN`, `RECORD-KEY-UNKNOWN`, `RECORD-TYPE-INVALID`, `CONFIG-BIB-FILES-MISSING`, `BIB-PARSER-MESSAGE`, `LATEX-CONVERSION-FAILED`, `TEXT-CONTROL-CHARACTER`, `BIB-WRITE-BACK-FAILED`, `BIB-STRING-REDEFINED`, `ID-GROUPING-AMBIGUOUS-DECLARED`, `RESOLVE-UNRESOLVED-NAME` |
204
+ | **Warning** | Listed under `Warnings`; not counted, and does not change the exit code. | Prefixed `Warning: ` on standard error; the run continues. | `BIB-YEAR-MISSING`, `BIB-YEAR-INVALID`, `BIB-DOI-INVALID`, `BIB-OTHERS-NOT-LAST`, `BIB-STRING-UNDEFINED`, `BIB-SYNTAX-ERROR`, `BIB-BRACE-MISMATCH`, `BIB-COMMENTED-COMMAND-READ`, `BIB-VENUE-MISSING`, `BIB-ENTRY-TYPE-UNSUPPORTED`, `LATEX-COMMAND-UNKNOWN`, `ID-GROUPING-SPANS-SPELLINGS`, `ID-GROUPING-INITIALS-AMBIGUOUS`, `RESOLVE-AMBIGUOUS-NAME`, `RESOLVE-SUGGESTION`, `RESOLVE-COLLABORATOR-ALIAS-IS-MEMBER`, `PEOPLE-ALIAS-AMBIGUOUS`, `PEOPLE-ROLE-INVALID`, `PEOPLE-STATUS-INVALID`, `PROJECTS-STATUS-INVALID`, `CONFIG-LAB-NAME-MISSING`, `CONFIG-KEY-UNKNOWN`, `RECORD-KEY-UNKNOWN`, `RECORD-TYPE-INVALID`, `CONFIG-BIB-FILES-MISSING`, `BIB-PARSER-MESSAGE`, `LATEX-CONVERSION-FAILED`, `LINK-SCHEME-UNSUPPORTED`, `TEXT-CONTROL-CHARACTER`, `BIB-WRITE-BACK-FAILED`, `BIB-STRING-REDEFINED`, `ID-GROUPING-AMBIGUOUS-DECLARED`, `RESOLVE-UNRESOLVED-NAME` |
176
205
 
177
206
  The same code always carries the same class. What varies with the mode is
178
207
  how the run reacts to it, which is why the class is not in the code, and
@@ -220,10 +249,12 @@ Codes in use:
220
249
  | `CONFIG-LAB-NAME-MISSING` | The `lab` header declares no `name`. A `lab` that is not a mapping at all is a different condition and is not reported under this code. A warning. |
221
250
  | `BIB-YEAR-INVALID` | An entry's `year` is present but is not an unsigned run of the ASCII digits `0`–`9` (`sslabdata.parsers.bibtex.YEAR_DIGITS`), such as `in press`, or `-5`, `+2020`, `2_020` and full-width `2020`, which Python's `int()` would read as numbers. The work is emitted with `year: null` and sorts last, as one with no year does. A warning. |
222
251
  | `BIB-DOI-INVALID` | An entry's `doi` is a DOI resolver URL with nothing after it, such as `https://doi.org/` (`sslabdata.parsers.bibtex.DOI_RESOLVERS`), so it names no DOI. Located at `<file>:<key>:doi`, naming the value. The work gets no DOI identifier and no link of kind `doi`, rather than an empty one; the entry is kept. A `doi` that is empty or blank is read as absent, and is not reported. A warning. |
252
+ | `LINK-SCHEME-UNSUPPORTED` | A link in `work.links` whose URL scheme is not `http`, `https` or `mailto` (`sslabdata.parsers.bibtex.LINK_SCHEMES`), such as `javascript:`, `data:` or `vbscript:`, which a renderer escaping the document (§2) should refuse to turn into a link. The scheme is the one Python's URL parser reads (`urllib.parse.urlsplit()`, in `sslabdata.parsers.bibtex.url_scheme()`) once surrounding whitespace is off, compared without regard to case; the parser drops a tab or line break anywhere in the URL first, as a browser does, so `java<tab>script:` is `javascript`. A URL with no scheme is a relative path and is not reported; that includes a protocol-relative `//host/path`, which takes the scheme of the page that links it. A scheme of one letter is a Windows drive, read by Windows rules as `sslabdata.config` reads one (`PureWindowsPath`), and not a scheme: `C:/papers`, `C:\papers` and the drive-relative `c:papers` are local paths and are not reported. No registered scheme is one letter long. Every link is checked, whether the entry wrote it (`url`, `video`, `pdf`) or sslabdata built it (`doi`, `arxiv`, and `pdf` from `pdf_base_url`). Located at `<file>:<key>:<field>`, the field the link came from, or at `<file>:<key>:` for the PDF link `pdf_base_url` builds, which no field of the entry wrote; the prose names the link's kind, which for `url` can be `video`, and the URL. Once per link. A person's `website` and `photo`, a project's `website` and `image`, and everything under `lab` are fields, not links, and are not checked. **The link is emitted unchanged**: this code only reports it. A warning. |
223
253
  | `BIB-OTHERS-NOT-LAST` | An `author` or `editor` list has `and others` somewhere other than at its end, the one place BibTeX reads it as "et al.". It names nobody there either, so it is dropped as a terminal one is, and the names around it are kept in their order, with `position` counting only them. Located at `<file>:<key>:author` or `:editor`, once per list however many times it occurs. A terminal `and others` is not reported. A warning. |
224
254
  | `BIB-STRING-UNDEFINED` | A field value names an `@string` macro that nothing defined earlier in the same file. Located at the entry and field that use it, and naming the macro. It is read as empty, as BibTeX reads it, and the entry and its neighbours are kept. A macro used inside another `@string` definition is located at the file alone. A warning. |
225
- | `BIB-STRING-REDEFINED` | One or more `@string` macros are defined more than once. One line per run, however many files and macros: the count, the macros and every redefinition as `file:line` (§7). Only definitions the parser reads count, so one inside an `@comment` group does not, while a well-formed `@string{…}` on a `%` line does: the parser reads it, and it changes the macro's value. Whether it should be read is #78. The last definition is used, as in BibTeX. A warning in every mode, including under `--strict`. |
226
- | `BIB-SYNTAX-ERROR` | Text the BibTeX parser cannot read. Inside an entry, located at that entry and at the field the parser was reading or had just read, which is where an unclosed brace or quote leaves it, or with the field left empty when the error comes before any field; the entry is kept as far as it was read, so that value may hold text meant for later fields. Outside any entry — an `@` that begins no well-formed command — located at the file alone and skipped. A syntax error the parser library raises on a `%` line outside any entry — prose that mentions `@article`, say, which the library reads as the start of a command — is not reported (`sslabdata.parsers.bibtex._on_comment_line()`), so the prose `tests/COVERAGE.md` rows `structure.comment_lines` and `structure.comment_mentions_command` describe says nothing. A **well-formed** command on such a line is read, as in classic BibTeX, which has no `%` comment outside an entry: `% @article{hidden, …}` is an entry. Whether it should be is #78. The prose gives the line. A warning. |
255
+ | `BIB-STRING-REDEFINED` | One or more `@string` macros are defined more than once. One line per run, however many files and macros: the count, the macros and every redefinition as `file:line` (§7). Only definitions the parser reads count, so one inside an `@comment` group does not, while a well-formed `@string{…}` on a `%` line does: it is read, as every well-formed command on such a line is, it changes the macro's value, and it also draws `BIB-COMMENTED-COMMAND-READ`. The last definition is used, as in BibTeX. A warning in every mode, including under `--strict`. |
256
+ | `BIB-SYNTAX-ERROR` | Text the BibTeX parser cannot read. Inside an entry, located at that entry and at the field the parser was reading or had just read, which is where an unclosed brace or quote leaves it, or with the field left empty when the error comes before any field; the entry is kept as far as it was read, so that value may hold text meant for later fields. Outside any entry — an `@` that begins no well-formed command — located at the file alone and skipped. A syntax error the parser library raises on a `%` line outside any entry — prose that mentions `@article`, say, which the library reads as the start of a command — is not reported (`sslabdata.parsers.bibtex._on_comment_line()`), so the prose `tests/COVERAGE.md` rows `structure.comment_lines` and `structure.comment_mentions_command` describe says nothing. A **well-formed** command on such a line is read, and reported under `BIB-COMMENTED-COMMAND-READ`. The prose gives the line. A warning. |
257
+ | `BIB-COMMENTED-COMMAND-READ` | A well-formed command on a line that starts with `%` outside any entry. It is read, as classic BibTeX reads it, which has no `%` comment outside an entry: `% @article{hidden, …}` is an entry, and becomes a work, a `% @string{…}` defines its macro, and a `% @preamble{…}` is read as any `@preamble` is. Each command read from such a line is reported once, so a user who meant to comment it out is told it was read (`sslabdata.parsers.bibtex._commented_commands()`): an entry located at its key, and an `@string` or `@preamble` at the file alone, naming the macro. The prose gives the line. An `@string` counts as `BIB-STRING-REDEFINED` counts it, when its definition was read in full. Prose on a `%` line that is no well-formed command is not one, and says nothing (`BIB-SYNTAX-ERROR`); an `@comment{…}` group, or a line with no `@`, leaves an entry out. A warning. |
227
258
  | `BIB-BRACE-MISMATCH` | An entry that BibTeX's brace matching reads differently from how it was written, with no syntax error: one opening brace too many reads the next field into a value, and one closing brace too many ends the entry, so the fields after it are not read. Braces are matched as BibTeX matches them, `\{` and `\}` included, and the entry is kept as read; this code only reports it. Two shapes are looked for (`sslabdata.parsers.bibtex._Parser._check_braces()`), and nothing else, so a well-formed file never draws one: a value holding `, <name> = {` or `, <name> = "`, located at `<name>`, the field read into it, and naming the value's field; and text after the entry's closing brace on the line it ends on, other than a `%` comment or the next `@` command, located at the first field lost when that text is shaped `, <name> =`, and otherwise at the entry's last field read, whose text it is, naming the line. Once per entry, located at the first field whose text did not arrive as written. An entry with a `BIB-SYNTAX-ERROR` is not checked: that code already says its values may hold text meant for later fields. A `BIB-STRING-UNDEFINED` does not say so, and an entry with one is checked: in `title = {\} # w}`, BibTeX counts `\}` as a brace, so `w` is read as a macro and the next `}` ends the entry. A warning. |
228
259
  | `BIB-VENUE-MISSING` | An `@article` has no `journal`, or an `@inproceedings` has no `booktitle` (`sslabdata.parsers.bibtex.REQUIRED_CONTAINER`). No other entry type is checked. A field present but empty counts as missing. The entry is kept, and its venue is read by the usual rule from any other container field it carries, or is `null`. A warning. |
229
260
  | `BIB-ENTRY-TYPE-UNSUPPORTED` | An entry's type is not one sslabdata documents. Those are `@article`, `@inproceedings`, `@conference`, `@proceedings`, `@incollection`, `@inbook`, `@book`, `@phdthesis`, `@mastersthesis`, `@techreport`, `@manual` and `@misc` (`sslabdata.parsers.bibtex.SUPPORTED_TYPES`); `@unpublished` and `@booklet`, for two, are not. Located at `<file>:<key>:entry_type`. The entry is kept, and its venue is read by the field rules alone. A warning. |
@@ -246,7 +277,7 @@ Codes in use:
246
277
  | `RECORD-KEY-REPEATED` | A key is given twice in one mapping of a people, projects or collaborators file, which YAML alone would read as its last value, silently dropping the first. Every YAML file sslabdata reads is read with one loader that finds these (`sslabdata.config.YAMLLoader`). Keys are compared as YAML reads them, so `1` and `0x1` are one key. A merge key (`<<`) is not a repeat: the keys it merges in are overridden by the mapping's own, as YAML merge keys are defined. One code for all three files. Located at `<file>:<id>:<key>` (a collaborator's name for its id), with the path below the record's top level joined by `.` and a list member's index in brackets; the record is named by nothing when the repeated key is its `id` (a collaborator's `name`), and a repeat outside any record is located at `<file>::<path>`. The prose names the key and both lines. Every repeat is reported; the record is not loaded, and the run is fatal, as for a missing field: which value was meant is not sslabdata's to guess. |
247
278
  | `CONFIG-NOT-A-MAPPING` | `lab.yaml` is not a mapping of keys, or is empty. Fatal at load. |
248
279
  | `CONFIG-KEY-MISSING` | A required key is absent: `bib_dir`, or the `name` or `category` of a `bib_files` entry (`lab.yaml:bib_files:name`). Fatal at load. |
249
- | `CONFIG-TYPE-INVALID` | A key has a value of the wrong type: `bib_dir`, `pdf_base_url`, `people_file`, `projects_file` or `collaborators_file` that is not a string, `lab` that is not a mapping, a `lab` key the schema types (`name`, `description`, `institution`, `department`, `website`, `email`, `address` and `logo` are strings, `links` a mapping) whose value is another type or empty, or `bib_files` that is not a list. A `bib_files` entry is a mapping of a string `name` and a string `category` and nothing else: one that is not a mapping, whose `name` or `category` is not a string, or that holds another key is this code too, located at `lab.yaml:bib_files:<field>`. Fatal at load. |
280
+ | `CONFIG-TYPE-INVALID` | A key has a value of the wrong type: `bib_dir`, `pdf_base_url`, `people_file`, `projects_file` or `collaborators_file` that is not a string, `lab` that is not a mapping, a `lab` key the schema types (`name`, `description`, `institution`, `department`, `website`, `email`, `address` and `logo` are strings, `links` a mapping) whose value is another type or empty, or `bib_files` that is not a list. A `bib_files` entry is a mapping of a string `name` and a string `category` and nothing else: one that is not a mapping, whose `name` or `category` is not a string, or that holds another key is this code too, located at `lab.yaml:bib_files:<field>`. A `pdf_base_url` that is empty or whitespace alone names no base and is this code too, at `lab.yaml:pdf_base_url:`, whether it comes from `lab.yaml` or from a `LabDataConfig` built in Python (`sslabdata.config.reject_empty_pdf_base_url()`): as for `CONFIG-FILE-NOT-FOUND`'s empty path, only a key left out, or `None`, means none, here no guessed PDF link. Fatal at load. |
250
281
  | `CONFIG-VALUE-NOT-JSON` | A value under `lab`, at any depth and `lab.links` included, that the document cannot carry the same way in both formats: NaN or an infinity, which JSON cannot write; a set (`!!set`), whose order is not stable between runs; binary (`!!binary`); any other type that is not text, a number, a boolean, null, a list or a mapping; or a mapping key that is not a string, which YAML and JSON would write differently. Located at `lab.yaml:lab:<path>`, or at the mapping holding a key that is not a string, with the value or key in the prose. A date or a timestamp is not this code: it is emitted as its ISO 8601 text (`2010-01-01`, `2024-05-01T09:30:00+00:00`), in both formats. Fatal at load, so `--validate` fails wherever `--output` could not write the document. Raised as a `sslabdata.ConfigurationError`. |
251
282
  | `CONFIG-KEY-REPEATED` | A key is given twice in one mapping of `lab.yaml`, at any depth, which YAML alone would read as its last value, silently dropping the first. Keys are compared as `RECORD-KEY-REPEATED` compares them, and a merge key (`<<`) is not a repeat. Located at `lab.yaml:<key>:<field>` as rule 1 above locates any key of the file, with the repeated key last (`lab.yaml:lab:name` for a `name` given twice under `lab`, `lab.yaml:people_file:` for a top-level key); the prose names the key and both lines. The first repeat in the file is reported. Fatal at load. Raised as a `sslabdata.ConfigurationError`. |
252
283
  | `CONFIG-KEY-UNKNOWN` | `lab.yaml` holds a key sslabdata does not read (`sslabdata.config.KNOWN_KEYS`), such as a misspelt `people_fil`. It is ignored. A warning. |
@@ -257,7 +288,11 @@ Codes in use:
257
288
  | `LATEX-CONVERSION-FAILED` | A text field or a name part whose LaTeX the converter could not read at all, or whose conversion left one of the converter's own markers behind — a command that read part of a set-aside URL as its argument, such as an accent written before `\url{…}` (`sslabdata.parsers.latex.latex_to_text()`). Its text is kept as written, with the braces taken off, so it may still hold LaTeX (§2, *Two degraded cases*). Located at the entry and field. A warning. |
258
289
  | `TEXT-CONTROL-CHARACTER` | A value read from the input holds a control character (§2, *No string read from the input carries a control character*): a `.bib` field value, located at `<file>:<key>:<field>`, or a YAML scalar, key or value, located as `RECORD-KEY-REPEATED` locates a key in a people, projects or collaborators file and as `CONFIG-KEY-REPEATED` locates one in `lab.yaml`. One line per value, naming each character as `U+XXXX`. The characters are removed and the rest of the value is kept. A warning. |
259
290
  | `BIB-WRITE-BACK-FAILED` | An entry that could not be written back out as BibTeX. Its `bibtex` is `null`. Located at `<file>:<key>:bibtex`. A warning. |
260
- | `OUTPUT-WRITE-FAILED` | `--output` names a destination the operating system will not let sslabdata write: a directory, a path under a file, a directory without write permission, a full disk. Located at the `--output` path alone; the prose is the operating system's reason, naming the path it refused when that is not the destination's own directory. Unprefixed on standard error, with no `Wrote …` line. The write is atomic, so a file already at the path is as it was and no temporary file is left behind. Only `--output` writes, so no other mode reports it. Fatal. |
291
+ | `OUTPUT-WRITE-FAILED` | `--output` names a destination the operating system will not let sslabdata write: a directory, a path under a file, a directory without write permission, a full disk. Located at the `--output` path alone; the prose is the operating system's reason, naming the path it refused when that is not the destination's own directory. Unprefixed on standard error, with no `Wrote …` line. The write is atomic, so a file already at the path is as it was and no temporary file is left behind. Only `--output` writes the document, so no other mode reports it; `init` reports its own writes as `INIT-WRITE-FAILED`. Fatal. |
292
+ | `INIT-FILE-EXISTS` | `sslabdata init` found a file already at a path it writes, and `--force` was not given. Located at that path alone, one line per file. Nothing is written. Fatal. |
293
+ | `INIT-PATH-WRONG-KIND` | `sslabdata init` found something other than a directory at `DIR` or at its `bib` directory, or something other than a regular file or a symlink at a path it writes, such as a directory. Located at that path alone. Nothing is written, with or without `--force`. Fatal. |
294
+ | `INIT-PATH-OUTSIDE-DIR` | A directory `sslabdata init` writes into, `DIR/bib`, resolves outside `DIR` once symlinks are followed, so a file written there would land outside `DIR`. Located at the file's path alone. Nothing is written, with or without `--force`. Fatal. |
295
+ | `INIT-WRITE-FAILED` | `sslabdata init` could not create `DIR` or write one of its files: a directory without write permission, a full disk. Located at that path alone; the prose is the operating system's reason. The file that failed is not left half written, and one already there is as it was; files written before it stay. Fatal. |
261
296
  | `CONFIG-NOT-FOUND` | The `--config` file does not exist. Located at that path alone; `Error: CONFIG-NOT-FOUND …` on standard error. Fatal at load. |
262
297
  | `CONFIG-UNREADABLE` | The `--config` file exists but cannot be read — not valid YAML, not UTF-8, or a file the operating system will not open — or another input file, such as a `people_file`, exists but the operating system will not open it. Located at the `--config` path alone; the prose is the reading library's own wording, on one line. Fatal at load. |
263
298
  | `CONFIG-BIB-FILE-ABSOLUTE` | A `bib_files[].name` is an absolute path, under POSIX or Windows rules. Fatal at load, because the name is emitted as `work.source.file`, which is promised never to be absolute (§5). Raised as a `sslabdata.ConfigurationError`, which the Python API paragraphs below say more about. |
@@ -517,7 +552,8 @@ converts nor checks them:
517
552
  `edition` and `howpublished`, which are outside `TEXT_FIELDS` and are
518
553
  emitted as the entry wrote them.
519
554
  - **Every identifier** in `identifiers`, and the `url` of a link whose
520
- `origin` is `input`.
555
+ `origin` is `input`. Of a link's `url`, only the scheme is checked, and it
556
+ is emitted as written whatever the check finds (`LINK-SCHEME-UNSUPPORTED`).
521
557
 
522
558
  For these the plain-Unicode rule is a **requirement on the input, not a
523
559
  guarantee sslabdata enforces**. If `people.yaml` says `name: "<b>Alice</b>"`,
@@ -551,7 +587,7 @@ rule does not apply to the input itself — only to whatever it produces.
551
587
  | The citation key and the entry type | Become `bib_id` (and `source.key`) and `entry_type` (`entry_fields()`); see heading 4. |
552
588
  | `person.aliases` | Read for matching by `sslabdata.resolver.match()`, never emitted — `Person.to_dict()` has no `aliases` key. |
553
589
  | `collaborators_file` entries | A list of `{name, aliases}` read by `sslabdata.loaders.load_collaborators()`. Read only to decide which unresolved authorships share one `collaborators` grouping (§5); never emitted as such and never a source of `person_id`. |
554
- | `bib_dir`, `people_file`, `projects_file`, `collaborators_file`, `pdf_base_url` | Configuration. Never emitted; `pdf_base_url` survives only inside the constructed PDF link. `bib_files[].name` **is** emitted, as `work.source.file`, and is therefore checked (`sslabdata.config.is_absolute_path()`, §5). |
590
+ | `bib_dir`, `people_file`, `projects_file`, `collaborators_file`, `pdf_base_url` | Configuration. Never emitted; `pdf_base_url` survives only inside the constructed PDF link, and leaving it out turns that guess off (§5). `bib_files[].name` **is** emitted, as `work.source.file`, and is therefore checked (`sslabdata.config.is_absolute_path()`, §5). |
555
591
  | Any BibTeX field named nowhere in this table or heading 1 — `keywords`, `annote`, `language` and the rest | Not interpreted by sslabdata outside the `bibtex` record. `entry_fields()` copies it and `format_bibtex()` serializes it, but nothing reads its value, so it affects no other property (§5). |
556
592
 
557
593
  The fields named in that table and in heading 1 are the complete set sslabdata
@@ -755,7 +791,7 @@ sslabdata's own output as input, and a wrong derivation becomes permanent.
755
791
  | `work.links` | **Derived** — a map from kind to link records, built from the entry's `url`, `video` and `pdf`, from `pdf_base_url` and from the identifiers above (`build_links()`). See below. |
756
792
  | `work.project_ids` | Input — the `project` field, split on commas (`parse_project_ids()`). |
757
793
  | `work.bibtex` | **Derived** — the entry re-serialized as BibTeX, or `null` when that failed (`format_bibtex()`). See below. |
758
- | `author.given`, `von`, `family`, `suffix`, `literal` | Input — the parts BibTeX split the name into, converted from LaTeX, with an equal-contribution marker removed (`person_name_parts()`). An entry writing `Brown, B.` yields `given: "B."`, and that is correct, not a gap. |
794
+ | `author.given`, `von`, `family`, `suffix`, `literal` | Input — the parts BibTeX split the name into, converted from LaTeX, with an equal-contribution marker removed (`person_name_parts()`). A spaced marker that BibTeX split between the particle and the surname is joined again, and those two parts are split as BibTeX splits the name with the marker unspaced (`_joined_groups()`). So is one that runs from the given name into the rest in a name written given name first, `Bob Brown\textsuperscript {*}`, whose words are then read again as BibTeX reads them with the marker unspaced (`_crosses_given_name()`). An entry writing `Brown, B.` yields `given: "B."`, and that is correct, not a gap. |
759
795
  | `author.name` | **Derived** — the parts joined in reading order (`readable_name()`). *A readable form of the input name, not a citation form*: it does not abbreviate, expand or normalise. |
760
796
  | `author.position` | **Derived** — where the authorship sits in its work's list, 1-based, counting only the names that reach the document. |
761
797
  | `author.person_id` | **Derived** — the resolver's match against `people_file` (`sslabdata.resolver.resolve_authors()`): on the structured full name, then — only for a name that is itself abbreviated — on a declared alias, and never when the name fits more than one person or only nearly matches. See *How a name is matched* below. |
@@ -898,7 +934,14 @@ or `missing` and a remote one yields `unchecked`; an entry's own `pdf` is
898
934
  `unchecked` whatever the base. `verification` is `{status}` and records no
899
935
  time. Three states replace a null: "no base configured" is no link at all for an entry without a `pdf`
900
936
  field, "the file is not there" is `missing`, and "nobody has looked" is
901
- `unchecked`. Verifying a remote link is #20.
937
+ `unchecked`. Nothing in sslabdata checks a remote link, since it makes no
938
+ network calls.
939
+
940
+ **`pdf_base_url` is the switch for guessed PDF links.** It is read for
941
+ nothing else, so a `lab.yaml` without it guesses no PDF link, and a work gets
942
+ one only from its own `pdf` field. There is no separate key to turn guessing
943
+ off. An empty or blank `pdf_base_url` is not that switch: it is
944
+ `CONFIG-TYPE-INVALID`.
902
945
 
903
946
  A link does **not** name the identifier it was built from. That is redundant
904
947
  with its kind and its origin, and it would be a cross-record constraint JSON
@@ -1095,6 +1138,19 @@ resolves, because GitHub redirects `labdata` to `sslabdata`. v3's `blob/main`
1095
1138
  `$id` does not resolve, as above. v5's `$id`, title and descriptions say
1096
1139
  `sslabdata`.
1097
1140
 
1141
+ **The input schemas are versioned apart from the document.**
1142
+ `schema/input/v1/` holds a JSON Schema for each input file: `lab.schema.json`,
1143
+ `people.schema.json`, `projects.schema.json` and `collaborators.schema.json`.
1144
+ They describe what the loaders accept; the loaders stay the authority, and
1145
+ the diagnostic codes above report what a schema cannot see. The input format
1146
+ and the document change for different reasons, so the input schemas carry
1147
+ their own version rather than `schema_version`. They follow the same rules:
1148
+ a published input schema is never edited, any change to one is a new
1149
+ version at a new path, and its `$id` is a pinned tag URL,
1150
+ `https://raw.githubusercontent.com/siddhss5/sslabdata/input-schema-v1/schema/input/v1/<file>.schema.json`,
1151
+ whose `input-schema-v1` tag is created when this version ships and is never
1152
+ moved.
1153
+
1098
1154
  **Version history.** What each `schema_version` changed is in
1099
1155
  [`CHANGELOG.md`](CHANGELOG.md).
1100
1156
 
@@ -1126,7 +1182,7 @@ any entry uses them, so the corpus does not distinguish the two readings.
1126
1182
  A redefinition is never silent. `sslabdata.parsers.bibtex._redefined_macros()`
1127
1183
  finds every definition of a macro after its first — among the definitions
1128
1184
  the parser itself reads, so an `@string` inside an `@comment` group is not
1129
- counted, and a line number counts lines as the parser does, after a byte
1185
+ counted, one on a `%` line is (`BIB-COMMENTED-COMMAND-READ`), and a line number counts lines as the parser does, after a byte
1130
1186
  order mark and with CRLF read as one line end — and `parse_all_works()`
1131
1187
  reports all of a run's in **one line**, under `BIB-STRING-REDEFINED`
1132
1188
  (`redefined_summary()`; `tests/COVERAGE.md` row `strings.redefined_report`):
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sslabdata"
3
- version = "3.0.0"
3
+ version = "3.1.0"
4
4
  description = "Renderer-agnostic academic lab data assembler: BibTeX + YAML → structured data"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -57,9 +57,10 @@ Changelog = "https://github.com/siddhss5/sslabdata/blob/main/CHANGELOG.md"
57
57
  requires = ["setuptools>=77"]
58
58
  build-backend = "setuptools.build_meta"
59
59
 
60
- # The current output schema is public wheel data, installed at
61
- # sslabdata/schema/v5/output.schema.json. The package directory is mapped onto
62
- # the repository's schema/ directory so the frozen file has one copy and one
60
+ # The current output schema and input schemas are public wheel data, installed
61
+ # at sslabdata/schema/v5/output.schema.json and
62
+ # sslabdata/schema/input/v1/*.schema.json. The package directory is mapped onto
63
+ # the repository's schema/ directory so each frozen file has one copy and one
63
64
  # path; earlier versions stay in the sdist only. schema/__init__.py makes the
64
65
  # mapped directory a regular package, which editable installs need to resolve.
65
66
  [tool.setuptools]
@@ -70,7 +71,9 @@ packages = ["sslabdata", "sslabdata.parsers", "sslabdata.schema"]
70
71
  "sslabdata.schema" = "schema"
71
72
 
72
73
  [tool.setuptools.package-data]
73
- "sslabdata.schema" = ["v5/output.schema.json"]
74
+ "sslabdata.schema" = ["v5/output.schema.json", "input/v1/*.schema.json"]
75
+ # The starting point `sslabdata init` copies.
76
+ "sslabdata" = ["templates/init/*.yaml", "templates/init/bib/*.bib"]
74
77
 
75
78
  [tool.mypy]
76
79
  files = ["sslabdata"]
@@ -0,0 +1,24 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://raw.githubusercontent.com/siddhss5/sslabdata/input-schema-v1/schema/input/v1/collaborators.schema.json",
4
+ "title": "sslabdata collaborators file",
5
+ "description": "The file lab.yaml names as collaborators_file: co-authors outside the lab whose spellings are grouped together, one record each. A record never makes anyone a lab member. It describes what sslabdata's loader accepts; the loader stays the authority, and reports each problem under a code in SPEC.md, Diagnostic codes. A record key not listed here is reported as RECORD-KEY-UNKNOWN and ignored, so it is allowed. Published input schemas are immutable: this one is served from the input-schema-v1 tag, which is created when this version ships and is never moved (SPEC.md §6).",
6
+ "type": ["array", "null"],
7
+ "items": { "$ref": "#/$defs/collaborator" },
8
+ "$defs": {
9
+ "nonEmptyString": { "type": "string", "pattern": "\\S" },
10
+ "collaborator": {
11
+ "type": "object",
12
+ "required": ["name"],
13
+ "additionalProperties": true,
14
+ "properties": {
15
+ "name": { "$ref": "#/$defs/nonEmptyString" },
16
+ "aliases": {
17
+ "description": "Other spellings of the name, grouped with it.",
18
+ "type": ["array", "null"],
19
+ "items": { "$ref": "#/$defs/nonEmptyString" }
20
+ }
21
+ }
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,71 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://raw.githubusercontent.com/siddhss5/sslabdata/input-schema-v1/schema/input/v1/lab.schema.json",
4
+ "title": "sslabdata lab.yaml",
5
+ "description": "The configuration file sslabdata is run with (--config). It describes what sslabdata's loader accepts; the loader stays the authority, and reports each problem under a code in SPEC.md, Diagnostic codes. A key not listed here is reported as CONFIG-KEY-UNKNOWN and ignored, so it is allowed. Published input schemas are immutable: this one is served from the input-schema-v1 tag, which is created when this version ships and is never moved (SPEC.md §6).",
6
+ "type": "object",
7
+ "required": ["bib_dir"],
8
+ "additionalProperties": true,
9
+ "properties": {
10
+ "lab": {
11
+ "description": "The lab header, copied into the document's lab unchanged (SPEC.md §5). The keys below are typed; any other key is allowed and copied through. sslabdata warns when it declares no name (CONFIG-LAB-NAME-MISSING).",
12
+ "type": ["object", "null"],
13
+ "additionalProperties": true,
14
+ "properties": {
15
+ "name": { "type": "string" },
16
+ "description": { "type": "string" },
17
+ "institution": { "type": "string" },
18
+ "department": { "type": "string" },
19
+ "website": { "type": "string" },
20
+ "email": { "type": "string" },
21
+ "address": { "type": "string" },
22
+ "logo": { "type": "string" },
23
+ "links": { "type": "object" }
24
+ }
25
+ },
26
+ "site": {
27
+ "description": "Read by renderers, not by sslabdata, and accepted without being checked."
28
+ },
29
+ "bib_dir": {
30
+ "description": "The directory the bib_files names are under, relative to the directory sslabdata is run from.",
31
+ "type": "string"
32
+ },
33
+ "bib_files": {
34
+ "description": "The .bib files to read. sslabdata warns when there are none (CONFIG-BIB-FILES-MISSING).",
35
+ "type": ["array", "null"],
36
+ "items": {
37
+ "type": "object",
38
+ "required": ["name", "category"],
39
+ "additionalProperties": false,
40
+ "properties": {
41
+ "name": {
42
+ "description": "A name under bib_dir, emitted as work.source.file: never absolute, and never leaving bib_dir (SPEC.md §5).",
43
+ "type": "string"
44
+ },
45
+ "category": {
46
+ "description": "Emitted as the category of every work in this file.",
47
+ "type": "string"
48
+ }
49
+ }
50
+ }
51
+ },
52
+ "pdf_base_url": {
53
+ "description": "A work with no pdf field gets this URL plus its citation key as its pdf link. Leave it out to guess no pdf links; an empty or blank value is an error.",
54
+ "type": ["string", "null"],
55
+ "minLength": 1,
56
+ "pattern": "\\S"
57
+ },
58
+ "people_file": {
59
+ "description": "The people file (people.schema.json), relative to the directory sslabdata is run from.",
60
+ "type": ["string", "null"]
61
+ },
62
+ "projects_file": {
63
+ "description": "The projects file (projects.schema.json), relative to the directory sslabdata is run from.",
64
+ "type": ["string", "null"]
65
+ },
66
+ "collaborators_file": {
67
+ "description": "The external co-authors file (collaborators.schema.json), relative to the directory sslabdata is run from.",
68
+ "type": ["string", "null"]
69
+ }
70
+ }
71
+ }
@@ -0,0 +1,47 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://raw.githubusercontent.com/siddhss5/sslabdata/input-schema-v1/schema/input/v1/people.schema.json",
4
+ "title": "sslabdata people file",
5
+ "description": "The file lab.yaml names as people_file: a list of lab members and alumni, one record each. It describes what sslabdata's loader accepts; the loader stays the authority, and reports each problem under a code in SPEC.md, Diagnostic codes. A record key not listed here is reported as RECORD-KEY-UNKNOWN and ignored, so it is allowed. What each field becomes in the document is in SPEC.md §5. Published input schemas are immutable: this one is served from the input-schema-v1 tag, which is created when this version ships and is never moved (SPEC.md §6).",
6
+ "type": ["array", "null"],
7
+ "items": { "$ref": "#/$defs/person" },
8
+ "$defs": {
9
+ "nonEmptyString": { "type": "string", "pattern": "\\S" },
10
+ "optionalString": { "type": ["string", "null"] },
11
+ "optionalYear": { "type": ["integer", "null"] },
12
+ "person": {
13
+ "type": "object",
14
+ "required": ["id", "name", "role"],
15
+ "additionalProperties": true,
16
+ "properties": {
17
+ "id": {
18
+ "description": "The person's id, unique in the file (PEOPLE-ID-DUPLICATE).",
19
+ "$ref": "#/$defs/nonEmptyString"
20
+ },
21
+ "name": { "$ref": "#/$defs/nonEmptyString" },
22
+ "aliases": {
23
+ "description": "Other spellings of the name, used to match BibTeX authors and never emitted.",
24
+ "type": ["array", "null"],
25
+ "items": { "$ref": "#/$defs/nonEmptyString" }
26
+ },
27
+ "role": {
28
+ "description": "Any non-empty string; there is no list of roles (PEOPLE-ROLE-INVALID).",
29
+ "$ref": "#/$defs/nonEmptyString"
30
+ },
31
+ "status": {
32
+ "description": "current when absent (PEOPLE-STATUS-INVALID).",
33
+ "enum": ["current", "alumni"]
34
+ },
35
+ "photo": { "$ref": "#/$defs/optionalString" },
36
+ "website": { "$ref": "#/$defs/optionalString" },
37
+ "email": { "$ref": "#/$defs/optionalString" },
38
+ "co_advisor": { "$ref": "#/$defs/optionalString" },
39
+ "start_year": { "$ref": "#/$defs/optionalYear" },
40
+ "end_year": { "$ref": "#/$defs/optionalYear" },
41
+ "degree": { "$ref": "#/$defs/optionalString" },
42
+ "thesis_title": { "$ref": "#/$defs/optionalString" },
43
+ "current_position": { "$ref": "#/$defs/optionalString" }
44
+ }
45
+ }
46
+ }
47
+ }
@@ -0,0 +1,34 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://raw.githubusercontent.com/siddhss5/sslabdata/input-schema-v1/schema/input/v1/projects.schema.json",
4
+ "title": "sslabdata projects file",
5
+ "description": "The file lab.yaml names as projects_file: a list of research projects, one record each. It describes what sslabdata's loader accepts; the loader stays the authority, and reports each problem under a code in SPEC.md, Diagnostic codes. A record key not listed here is reported as RECORD-KEY-UNKNOWN and ignored, so it is allowed. What each field becomes in the document is in SPEC.md §5. Published input schemas are immutable: this one is served from the input-schema-v1 tag, which is created when this version ships and is never moved (SPEC.md §6).",
6
+ "type": ["array", "null"],
7
+ "items": { "$ref": "#/$defs/project" },
8
+ "$defs": {
9
+ "nonEmptyString": { "type": "string", "pattern": "\\S" },
10
+ "optionalString": { "type": ["string", "null"] },
11
+ "project": {
12
+ "type": "object",
13
+ "required": ["id", "title"],
14
+ "additionalProperties": true,
15
+ "properties": {
16
+ "id": {
17
+ "description": "The project's id, unique in the file (PROJECTS-ID-DUPLICATE). A work's project field names it.",
18
+ "$ref": "#/$defs/nonEmptyString"
19
+ },
20
+ "title": { "$ref": "#/$defs/nonEmptyString" },
21
+ "description": { "$ref": "#/$defs/optionalString" },
22
+ "website": { "$ref": "#/$defs/optionalString" },
23
+ "image": {
24
+ "description": "A URL or a site path, carried as plain text.",
25
+ "$ref": "#/$defs/optionalString"
26
+ },
27
+ "status": {
28
+ "description": "active when absent (PROJECTS-STATUS-INVALID).",
29
+ "enum": ["active", "completed"]
30
+ }
31
+ }
32
+ }
33
+ }
34
+ }
@@ -37,4 +37,4 @@ __all__ = [
37
37
  "export_to_yaml",
38
38
  "export_to_json",
39
39
  ]
40
- __version__ = "3.0.0"
40
+ __version__ = "3.1.0"
@@ -17,7 +17,8 @@ from pathlib import Path
17
17
  from typing import Dict, List, Optional, Tuple
18
18
 
19
19
  from .config import (
20
- LabDataConfig, reject_absolute_name, reject_name_outside_bib_dir,
20
+ LabDataConfig, reject_absolute_name, reject_empty_pdf_base_url,
21
+ reject_name_outside_bib_dir,
21
22
  )
22
23
  from .diagnostics import (
23
24
  ERROR, Diagnostic, diagnostic, in_report_order, severity,
@@ -359,6 +360,7 @@ def assemble_result(config: LabDataConfig) -> AssemblyResult:
359
360
  for bib_file in config.bib_files:
360
361
  reject_name_outside_bib_dir(getattr(bib_file, 'name', None),
361
362
  config.bib_dir)
363
+ reject_empty_pdf_base_url(config.pdf_base_url)
362
364
 
363
365
  found: List[Diagnostic] = []
364
366
  source = config.path or 'lab.yaml'