risforge 0.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.
- risforge-0.1.0/LICENSE +21 -0
- risforge-0.1.0/PKG-INFO +182 -0
- risforge-0.1.0/README.md +146 -0
- risforge-0.1.0/pyproject.toml +70 -0
- risforge-0.1.0/setup.cfg +4 -0
- risforge-0.1.0/src/risforge/__init__.py +38 -0
- risforge-0.1.0/src/risforge/cleaning.py +318 -0
- risforge-0.1.0/src/risforge/cli.py +197 -0
- risforge-0.1.0/src/risforge/enrichment.py +420 -0
- risforge-0.1.0/src/risforge/exceptions.py +23 -0
- risforge-0.1.0/src/risforge/pipeline.py +88 -0
- risforge-0.1.0/src/risforge.egg-info/PKG-INFO +182 -0
- risforge-0.1.0/src/risforge.egg-info/SOURCES.txt +18 -0
- risforge-0.1.0/src/risforge.egg-info/dependency_links.txt +1 -0
- risforge-0.1.0/src/risforge.egg-info/entry_points.txt +2 -0
- risforge-0.1.0/src/risforge.egg-info/requires.txt +10 -0
- risforge-0.1.0/src/risforge.egg-info/top_level.txt +1 -0
- risforge-0.1.0/tests/test_cleaning.py +106 -0
- risforge-0.1.0/tests/test_cli.py +37 -0
- risforge-0.1.0/tests/test_enrichment.py +154 -0
risforge-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Amyr
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
risforge-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: risforge
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Clean, deduplicate, and enrich RIS bibliographic files for systematic reviews.
|
|
5
|
+
Author-email: Amyr <amyrhexa@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/amyr/risforge
|
|
8
|
+
Project-URL: Repository, https://github.com/amyr/risforge
|
|
9
|
+
Project-URL: Issues, https://github.com/amyr/risforge/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/amyr/risforge/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: ris,bibliography,systematic-review,deduplication,crossref,openalex,citation-management,prisma
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering
|
|
15
|
+
Classifier: Topic :: Text Processing :: Filters
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Operating System :: OS Independent
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: rispy>=0.9.0
|
|
27
|
+
Requires-Dist: requests>=2.31
|
|
28
|
+
Requires-Dist: requests-cache>=1.1
|
|
29
|
+
Requires-Dist: urllib3>=2.0
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
32
|
+
Requires-Dist: pytest-cov>=5.0; extra == "dev"
|
|
33
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
34
|
+
Requires-Dist: twine>=5.0; extra == "dev"
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
|
|
37
|
+
# risforge
|
|
38
|
+
|
|
39
|
+
**Clean, deduplicate, and enrich RIS bibliographic files for systematic reviews.**
|
|
40
|
+
|
|
41
|
+
`risforge` takes a raw `.ris` export from Scopus, Web of Science, PubMed,
|
|
42
|
+
EndNote, or any other reference manager and:
|
|
43
|
+
|
|
44
|
+
1. **Cleans & deduplicates** it — normalizing titles, DOIs, and author
|
|
45
|
+
names, then clustering duplicate records (by exact DOI, and by a
|
|
46
|
+
title + first-author fallback) and merging each cluster into one
|
|
47
|
+
complete, data-loss-free record.
|
|
48
|
+
2. **Enriches** it — filling in missing abstracts, journal names,
|
|
49
|
+
volumes/issues/pages, ISSNs, keywords, and open-access PDF links by
|
|
50
|
+
querying [Crossref](https://www.crossref.org/), [OpenAlex](https://openalex.org/),
|
|
51
|
+
[Semantic Scholar](https://www.semanticscholar.org/), and
|
|
52
|
+
[Unpaywall](https://unpaywall.org/). Existing fields are never
|
|
53
|
+
overwritten — only gaps are filled.
|
|
54
|
+
|
|
55
|
+
It's built for the kind of unglamorous but essential prep work that
|
|
56
|
+
comes before title/abstract screening in a systematic review: getting
|
|
57
|
+
one clean, complete, deduplicated `.ris` file out of a pile of messy,
|
|
58
|
+
overlapping database exports.
|
|
59
|
+
|
|
60
|
+
## Features
|
|
61
|
+
|
|
62
|
+
- **DOI-first deduplication** with a normalized title + first-author
|
|
63
|
+
fallback for records that lack a DOI, using union-find clustering so
|
|
64
|
+
transitively-linked duplicates across three, four, or more sources
|
|
65
|
+
all collapse into one record.
|
|
66
|
+
- **Zero data loss on merge** — the most complete record in a
|
|
67
|
+
duplicate cluster is used as the base, and every other record in the
|
|
68
|
+
cluster supplements it with whatever fields it's missing.
|
|
69
|
+
- **Non-destructive enrichment** — only empty fields are filled;
|
|
70
|
+
anything you (or an upstream export) already populated is left
|
|
71
|
+
alone.
|
|
72
|
+
- **Resilient HTTP** — automatic retries with exponential backoff on
|
|
73
|
+
429/5xx responses, and a 7-day on-disk response cache so re-running
|
|
74
|
+
a pipeline doesn't re-hit the same APIs for records you've already
|
|
75
|
+
enriched.
|
|
76
|
+
- **Library or CLI** — use it as `import risforge` in a script/notebook,
|
|
77
|
+
or as a single `risforge` command.
|
|
78
|
+
|
|
79
|
+
## Installation
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
pip install risforge
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Requires Python 3.10+.
|
|
86
|
+
|
|
87
|
+
## Quick start
|
|
88
|
+
|
|
89
|
+
### Command line
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# Clean and deduplicate only
|
|
93
|
+
risforge clean raw_export.ris clean.ris
|
|
94
|
+
|
|
95
|
+
# Enrich an already-clean file
|
|
96
|
+
risforge enrich clean.ris enriched.ris --email you@example.com
|
|
97
|
+
|
|
98
|
+
# Both steps in one call
|
|
99
|
+
risforge pipeline raw_export.ris --email you@example.com
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The `pipeline` subcommand writes `<input>_clean.ris` and
|
|
103
|
+
`<input>_enriched.ris` next to your input file by default; pass
|
|
104
|
+
`--dedup-output` / `--output` to control that explicitly.
|
|
105
|
+
|
|
106
|
+
An email address is required by Crossref, OpenAlex, and Unpaywall's
|
|
107
|
+
"polite pool" usage policies — it's sent as a contact address in your
|
|
108
|
+
requests, never stored or transmitted anywhere else.
|
|
109
|
+
|
|
110
|
+
### Python API
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
from risforge import clean_ris_file, RisEnricher, run_pipeline
|
|
114
|
+
|
|
115
|
+
# Clean + deduplicate only
|
|
116
|
+
records, errors = clean_ris_file("raw_export.ris", "clean.ris")
|
|
117
|
+
print(f"{len(records)} unique records, {len(errors)} parse errors")
|
|
118
|
+
|
|
119
|
+
# Enrich only
|
|
120
|
+
enricher = RisEnricher(email="you@example.com")
|
|
121
|
+
stats = enricher.enrich_file("clean.ris", "enriched.ris")
|
|
122
|
+
print(f"Enriched {stats['enriched']}/{stats['processed']} records")
|
|
123
|
+
|
|
124
|
+
# Both, in one call
|
|
125
|
+
result = run_pipeline(
|
|
126
|
+
input_path="raw_export.ris",
|
|
127
|
+
dedup_path="clean.ris",
|
|
128
|
+
enriched_path="enriched.ris",
|
|
129
|
+
email="you@example.com",
|
|
130
|
+
)
|
|
131
|
+
print(result.cleaned_record_count, result.enrichment_stats)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Configuration
|
|
135
|
+
|
|
136
|
+
`RisEnricher` accepts a few constructor arguments beyond `email`:
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
RisEnricher(
|
|
140
|
+
email="you@example.com",
|
|
141
|
+
cache_name=".api_cache", # base filename for the on-disk HTTP cache
|
|
142
|
+
session=None, # inject your own requests.Session (mainly for testing)
|
|
143
|
+
)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`enrich_file()` also accepts `fail_report_path` (where unresolved-DOI
|
|
147
|
+
records are written as JSON) and `request_delay_seconds` (delay
|
|
148
|
+
between records; defaults to 0.1s to stay within provider rate limits).
|
|
149
|
+
|
|
150
|
+
## Troubleshooting
|
|
151
|
+
|
|
152
|
+
- **"Unresolved DOI via Title Matching" in the failure report** — the
|
|
153
|
+
record had no DOI and its title didn't match any Crossref result
|
|
154
|
+
above the 90% similarity threshold closely enough to resolve one.
|
|
155
|
+
These records are returned unmodified rather than guessed at.
|
|
156
|
+
- **Enrichment seems slow** — each record makes up to 5 API calls
|
|
157
|
+
(1 for DOI resolution if needed, 4 for metadata) with a small delay
|
|
158
|
+
between records. Re-running against the same input is fast, since
|
|
159
|
+
responses are cached for 7 days.
|
|
160
|
+
- **A field I already had got left alone even though enrichment "ran"** —
|
|
161
|
+
that's intentional. Enrichment only fills empty fields; it never
|
|
162
|
+
overwrites existing data.
|
|
163
|
+
|
|
164
|
+
## Contributing
|
|
165
|
+
|
|
166
|
+
1. Clone the repo and install in editable mode with dev dependencies:
|
|
167
|
+
```bash
|
|
168
|
+
pip install -e ".[dev]"
|
|
169
|
+
```
|
|
170
|
+
2. Run the test suite:
|
|
171
|
+
```bash
|
|
172
|
+
pytest
|
|
173
|
+
```
|
|
174
|
+
3. Open a PR. Please include tests for any behavioral change, and note
|
|
175
|
+
any deduplication/merge/enrichment logic changes explicitly in
|
|
176
|
+
`CHANGELOG.md` — this package's core value is *predictability* for
|
|
177
|
+
systematic review workflows, so silent behavior changes are treated
|
|
178
|
+
as bugs.
|
|
179
|
+
|
|
180
|
+
## License
|
|
181
|
+
|
|
182
|
+
MIT — see [LICENSE](LICENSE).
|
risforge-0.1.0/README.md
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# risforge
|
|
2
|
+
|
|
3
|
+
**Clean, deduplicate, and enrich RIS bibliographic files for systematic reviews.**
|
|
4
|
+
|
|
5
|
+
`risforge` takes a raw `.ris` export from Scopus, Web of Science, PubMed,
|
|
6
|
+
EndNote, or any other reference manager and:
|
|
7
|
+
|
|
8
|
+
1. **Cleans & deduplicates** it — normalizing titles, DOIs, and author
|
|
9
|
+
names, then clustering duplicate records (by exact DOI, and by a
|
|
10
|
+
title + first-author fallback) and merging each cluster into one
|
|
11
|
+
complete, data-loss-free record.
|
|
12
|
+
2. **Enriches** it — filling in missing abstracts, journal names,
|
|
13
|
+
volumes/issues/pages, ISSNs, keywords, and open-access PDF links by
|
|
14
|
+
querying [Crossref](https://www.crossref.org/), [OpenAlex](https://openalex.org/),
|
|
15
|
+
[Semantic Scholar](https://www.semanticscholar.org/), and
|
|
16
|
+
[Unpaywall](https://unpaywall.org/). Existing fields are never
|
|
17
|
+
overwritten — only gaps are filled.
|
|
18
|
+
|
|
19
|
+
It's built for the kind of unglamorous but essential prep work that
|
|
20
|
+
comes before title/abstract screening in a systematic review: getting
|
|
21
|
+
one clean, complete, deduplicated `.ris` file out of a pile of messy,
|
|
22
|
+
overlapping database exports.
|
|
23
|
+
|
|
24
|
+
## Features
|
|
25
|
+
|
|
26
|
+
- **DOI-first deduplication** with a normalized title + first-author
|
|
27
|
+
fallback for records that lack a DOI, using union-find clustering so
|
|
28
|
+
transitively-linked duplicates across three, four, or more sources
|
|
29
|
+
all collapse into one record.
|
|
30
|
+
- **Zero data loss on merge** — the most complete record in a
|
|
31
|
+
duplicate cluster is used as the base, and every other record in the
|
|
32
|
+
cluster supplements it with whatever fields it's missing.
|
|
33
|
+
- **Non-destructive enrichment** — only empty fields are filled;
|
|
34
|
+
anything you (or an upstream export) already populated is left
|
|
35
|
+
alone.
|
|
36
|
+
- **Resilient HTTP** — automatic retries with exponential backoff on
|
|
37
|
+
429/5xx responses, and a 7-day on-disk response cache so re-running
|
|
38
|
+
a pipeline doesn't re-hit the same APIs for records you've already
|
|
39
|
+
enriched.
|
|
40
|
+
- **Library or CLI** — use it as `import risforge` in a script/notebook,
|
|
41
|
+
or as a single `risforge` command.
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install risforge
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Requires Python 3.10+.
|
|
50
|
+
|
|
51
|
+
## Quick start
|
|
52
|
+
|
|
53
|
+
### Command line
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# Clean and deduplicate only
|
|
57
|
+
risforge clean raw_export.ris clean.ris
|
|
58
|
+
|
|
59
|
+
# Enrich an already-clean file
|
|
60
|
+
risforge enrich clean.ris enriched.ris --email you@example.com
|
|
61
|
+
|
|
62
|
+
# Both steps in one call
|
|
63
|
+
risforge pipeline raw_export.ris --email you@example.com
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The `pipeline` subcommand writes `<input>_clean.ris` and
|
|
67
|
+
`<input>_enriched.ris` next to your input file by default; pass
|
|
68
|
+
`--dedup-output` / `--output` to control that explicitly.
|
|
69
|
+
|
|
70
|
+
An email address is required by Crossref, OpenAlex, and Unpaywall's
|
|
71
|
+
"polite pool" usage policies — it's sent as a contact address in your
|
|
72
|
+
requests, never stored or transmitted anywhere else.
|
|
73
|
+
|
|
74
|
+
### Python API
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from risforge import clean_ris_file, RisEnricher, run_pipeline
|
|
78
|
+
|
|
79
|
+
# Clean + deduplicate only
|
|
80
|
+
records, errors = clean_ris_file("raw_export.ris", "clean.ris")
|
|
81
|
+
print(f"{len(records)} unique records, {len(errors)} parse errors")
|
|
82
|
+
|
|
83
|
+
# Enrich only
|
|
84
|
+
enricher = RisEnricher(email="you@example.com")
|
|
85
|
+
stats = enricher.enrich_file("clean.ris", "enriched.ris")
|
|
86
|
+
print(f"Enriched {stats['enriched']}/{stats['processed']} records")
|
|
87
|
+
|
|
88
|
+
# Both, in one call
|
|
89
|
+
result = run_pipeline(
|
|
90
|
+
input_path="raw_export.ris",
|
|
91
|
+
dedup_path="clean.ris",
|
|
92
|
+
enriched_path="enriched.ris",
|
|
93
|
+
email="you@example.com",
|
|
94
|
+
)
|
|
95
|
+
print(result.cleaned_record_count, result.enrichment_stats)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Configuration
|
|
99
|
+
|
|
100
|
+
`RisEnricher` accepts a few constructor arguments beyond `email`:
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
RisEnricher(
|
|
104
|
+
email="you@example.com",
|
|
105
|
+
cache_name=".api_cache", # base filename for the on-disk HTTP cache
|
|
106
|
+
session=None, # inject your own requests.Session (mainly for testing)
|
|
107
|
+
)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`enrich_file()` also accepts `fail_report_path` (where unresolved-DOI
|
|
111
|
+
records are written as JSON) and `request_delay_seconds` (delay
|
|
112
|
+
between records; defaults to 0.1s to stay within provider rate limits).
|
|
113
|
+
|
|
114
|
+
## Troubleshooting
|
|
115
|
+
|
|
116
|
+
- **"Unresolved DOI via Title Matching" in the failure report** — the
|
|
117
|
+
record had no DOI and its title didn't match any Crossref result
|
|
118
|
+
above the 90% similarity threshold closely enough to resolve one.
|
|
119
|
+
These records are returned unmodified rather than guessed at.
|
|
120
|
+
- **Enrichment seems slow** — each record makes up to 5 API calls
|
|
121
|
+
(1 for DOI resolution if needed, 4 for metadata) with a small delay
|
|
122
|
+
between records. Re-running against the same input is fast, since
|
|
123
|
+
responses are cached for 7 days.
|
|
124
|
+
- **A field I already had got left alone even though enrichment "ran"** —
|
|
125
|
+
that's intentional. Enrichment only fills empty fields; it never
|
|
126
|
+
overwrites existing data.
|
|
127
|
+
|
|
128
|
+
## Contributing
|
|
129
|
+
|
|
130
|
+
1. Clone the repo and install in editable mode with dev dependencies:
|
|
131
|
+
```bash
|
|
132
|
+
pip install -e ".[dev]"
|
|
133
|
+
```
|
|
134
|
+
2. Run the test suite:
|
|
135
|
+
```bash
|
|
136
|
+
pytest
|
|
137
|
+
```
|
|
138
|
+
3. Open a PR. Please include tests for any behavioral change, and note
|
|
139
|
+
any deduplication/merge/enrichment logic changes explicitly in
|
|
140
|
+
`CHANGELOG.md` — this package's core value is *predictability* for
|
|
141
|
+
systematic review workflows, so silent behavior changes are treated
|
|
142
|
+
as bugs.
|
|
143
|
+
|
|
144
|
+
## License
|
|
145
|
+
|
|
146
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "risforge"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Clean, deduplicate, and enrich RIS bibliographic files for systematic reviews."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Amyr", email = "amyrhexa@gmail.com" }
|
|
14
|
+
]
|
|
15
|
+
keywords = [
|
|
16
|
+
"ris",
|
|
17
|
+
"bibliography",
|
|
18
|
+
"systematic-review",
|
|
19
|
+
"deduplication",
|
|
20
|
+
"crossref",
|
|
21
|
+
"openalex",
|
|
22
|
+
"citation-management",
|
|
23
|
+
"prisma",
|
|
24
|
+
]
|
|
25
|
+
classifiers = [
|
|
26
|
+
"Development Status :: 4 - Beta",
|
|
27
|
+
"Intended Audience :: Science/Research",
|
|
28
|
+
"Topic :: Scientific/Engineering",
|
|
29
|
+
"Topic :: Text Processing :: Filters",
|
|
30
|
+
"License :: OSI Approved :: MIT License",
|
|
31
|
+
"Programming Language :: Python :: 3",
|
|
32
|
+
"Programming Language :: Python :: 3.10",
|
|
33
|
+
"Programming Language :: Python :: 3.11",
|
|
34
|
+
"Programming Language :: Python :: 3.12",
|
|
35
|
+
"Programming Language :: Python :: 3.13",
|
|
36
|
+
"Operating System :: OS Independent",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
dependencies = [
|
|
40
|
+
"rispy>=0.9.0",
|
|
41
|
+
"requests>=2.31",
|
|
42
|
+
"requests-cache>=1.1",
|
|
43
|
+
"urllib3>=2.0",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
[project.optional-dependencies]
|
|
47
|
+
dev = [
|
|
48
|
+
"pytest>=8.0",
|
|
49
|
+
"pytest-cov>=5.0",
|
|
50
|
+
"build>=1.2",
|
|
51
|
+
"twine>=5.0",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
[project.urls]
|
|
55
|
+
Homepage = "https://github.com/amyr/risforge"
|
|
56
|
+
Repository = "https://github.com/amyr/risforge"
|
|
57
|
+
Issues = "https://github.com/amyr/risforge/issues"
|
|
58
|
+
Changelog = "https://github.com/amyr/risforge/blob/main/CHANGELOG.md"
|
|
59
|
+
|
|
60
|
+
[project.scripts]
|
|
61
|
+
risforge = "risforge.cli:main"
|
|
62
|
+
|
|
63
|
+
[tool.setuptools.packages.find]
|
|
64
|
+
where = ["src"]
|
|
65
|
+
|
|
66
|
+
[tool.setuptools.package-dir]
|
|
67
|
+
"" = "src"
|
|
68
|
+
|
|
69
|
+
[tool.pytest.ini_options]
|
|
70
|
+
testpaths = ["tests"]
|
risforge-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""risforge: clean, deduplicate, and enrich RIS bibliographic files.
|
|
2
|
+
|
|
3
|
+
Typical usage::
|
|
4
|
+
|
|
5
|
+
from risforge import clean_ris_file, RisEnricher, run_pipeline
|
|
6
|
+
|
|
7
|
+
# Clean + deduplicate only
|
|
8
|
+
records, errors = clean_ris_file("raw.ris", "clean.ris")
|
|
9
|
+
|
|
10
|
+
# Enrich only
|
|
11
|
+
enricher = RisEnricher(email="you@example.com")
|
|
12
|
+
stats = enricher.enrich_file("clean.ris", "enriched.ris")
|
|
13
|
+
|
|
14
|
+
# Both, in one call
|
|
15
|
+
result = run_pipeline(
|
|
16
|
+
"raw.ris", "clean.ris", "enriched.ris", email="you@example.com"
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
See the ``risforge`` console script (``risforge --help``) for the
|
|
20
|
+
equivalent command-line interface.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from risforge.cleaning import clean_ris_file, process_ris_file
|
|
24
|
+
from risforge.enrichment import RisEnricher
|
|
25
|
+
from risforge.exceptions import RisForgeError, RisParsingError
|
|
26
|
+
from risforge.pipeline import PipelineResult, run_pipeline
|
|
27
|
+
|
|
28
|
+
__version__ = "0.1.0"
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"clean_ris_file",
|
|
32
|
+
"process_ris_file",
|
|
33
|
+
"RisEnricher",
|
|
34
|
+
"run_pipeline",
|
|
35
|
+
"PipelineResult",
|
|
36
|
+
"RisForgeError",
|
|
37
|
+
"RisParsingError",
|
|
38
|
+
]
|