From 2d96c589739b7200d7e3b320e6e0390d946fb1e6 Mon Sep 17 00:00:00 2001 From: "Felipe S. S. Schneider" Date: Fri, 28 Apr 2023 11:37:49 -0300 Subject: [PATCH 01/29] feat: add some old updates --- README.md | 56 ++++++++++++++++++++++++++++++---------------------- src/doi2bib | 5 ++++- src/doi2text | 14 ++++++++----- 3 files changed, 45 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 4ae355c..7648661 100644 --- a/README.md +++ b/README.md @@ -1,49 +1,53 @@ [![DOI](https://zenodo.org/badge/67431697.svg)](https://zenodo.org/badge/latestdoi/67431697) -# doi2bib and friends - -This repository provides scripts for converting from either [Digital Object Identifier (DOI)](https://www.doi.org) or [Uniform Resource Locator (URL)](https://en.wikipedia.org/wiki/URL) to [BibTeX](http://www.bibtex.org), or from DOI to plain text. -

ASCII video

-## doi2bib +TODO: support storing to the clipboard automatically. +TODO: add the surname of the first author automatically if not given (if only the year is shown as id when retrieved) -Convert [Digital Object Identifier (DOI)](https://www.doi.org) to -[BibTeX](http://www.bibtex.org) using -[crosscite.org](https://citation.crosscite.org/). -DOIs are received and the corresponding BibTeX entries are output. +# Bibliography in your command-line -Inspired by and -. +This repository provides scripts for converting from either [Digital Object Identifier (DOI)](https://www.doi.org) or [Uniform Resource Locator (URL)](https://en.wikipedia.org/wiki/URL) to [BibTeX](http://www.bibtex.org), or from DOI to plain text. + +## `doi2bib` -**October 16, 2017**: seems not to be working -any more. +Convert [Digital Object Identifier (DOI)](https://www.doi.org) to [BibTeX](http://www.bibtex.org) using [crosscite.org](https://citation.crosscite.org/). +DOIs are received and the corresponding BibTeX entries are output. +(Inspired by and .) -## doi2text +## `doi2text` -Convert [Digital Object Identifier (DOI)](https://www.doi.org) to plain text -using [crosscite.org](https://citation.crosscite.org/). DOIs are received and -the corresponding text entries are output. In theory, all the styles -available at can be used. +Convert [Digital Object Identifier (DOI)](https://www.doi.org) to plain text using [crosscite.org](https://citation.crosscite.org/). +DOIs are received and the corresponding text entries are output. In theory, all the styles available at can be used. -## url2bib +## `url2bib` -Convert [Uniform Resource Locator (URL)](https://en.wikipedia.org/wiki/URL) to -[BibTeX](http://www.bibtex.org) using pure -[Bash](https://en.wikipedia.org/wiki/Bash_(Unix_shell)). +Convert [Uniform Resource Locator (URL)](https://en.wikipedia.org/wiki/URL) to [BibTeX](http://www.bibtex.org) using pure [Bash](). URLs are received and the corresponding BibTeX entries are output. +(This is made to be used exactly as one would use `doi2bib`.) -(This is made to be used exactly as one would use doi2bib.) +This requires `gawk` (`sudo apt install gawk`). + +## Dependencies + +`doi2bib` requires [curl](https://curl.haxx.se/): + + $ sudo apt install curl + +You will also need `make` to install: + + $ sudo apt install make ## Installation After downloading the tarball and using `cd` to go where the files are, simply do a `sudo make install` and everything will be installed. +By default, it will install in `/usr/local/bin/`. ## Usage - $ doi2bib 10.1021/acs.jpcc.8b06244 10.1021/acscatal.9b00210 + $ doi2bib 10.1021/acs.jpcc.8b06244 10.1021/acscatal.9b00210 @article{Schneider_2018, title={How Do Secondary Phosphine Oxides Interact with Silver Nanoclusters? Insights from Computation}, volume={122}, @@ -92,3 +96,7 @@ After downloading the tarball and using `cd` to go where the files are, simply d howpublished = {\url{https://github.com/schneiderfelipe/doi2bib}}, note = {Accessed: 2020-01-27} } + +## Citing doi2bib + +If you use doi2bib in your research, please cite it. diff --git a/src/doi2bib b/src/doi2bib index 5e04bf2..9371b85 100755 --- a/src/doi2bib +++ b/src/doi2bib @@ -71,9 +71,12 @@ for doi in "$@"; do # we do some encoding as needed. doi=${doi//+/%2B} http_headers="Accept: text/bibliography; style=bibtex" + + echo "$http_headers" + echo # we retrieve the data from https://doi.org/ - curl -s -LH "$http_headers" "https://doi.org/${doi}" | \ + curl -LH "$http_headers" "https://doi.org/${doi}" | \ sed -e $'s/^ *//' \ -e $'s/}, /},\\\n /g' \ -e $'s/, /,\\\n /1' \ diff --git a/src/doi2text b/src/doi2text index a7624ab..06a030e 100755 --- a/src/doi2text +++ b/src/doi2text @@ -34,7 +34,7 @@ #? -v show version #? -s set ciation style from the ones given in #? (this is -#? "chicago-author-date" by default, see examples below) +#? "american-chemical-society" by default, see examples below) #? -S list all available citation styles and quit #? -l set locale from the ones given in #? , e.g. "pt-BR" @@ -55,8 +55,9 @@ #? doi2text and doi2bib #? are licensed under the MIT License. +style="american-chemical-society" + locale=$(locale | grep LANGUAGE | cut -d= -f2 | cut -d: -f1 | tr '_' '-') -style="chicago-author-date" if [ ! $locale ]; then locale="en-US" fi @@ -75,7 +76,7 @@ while getopts ":hvs:Sl:L" opt "$@"; do style="${OPTARG}" ;; S) # list all styles - curl -s -X GET \ + curl -X GET \ https://api.github.com/repos/citation-style-language/styles/contents/\?ref\=master | \ sed -e '/"name":.*\.csl"/{s/\s*"name": "//;s/\.csl",\s*//;p};d' exit 0 @@ -84,7 +85,7 @@ while getopts ":hvs:Sl:L" opt "$@"; do locale="${OPTARG}" ;; L) # list all styles - curl -s -X GET \ + curl -X GET \ https://api.github.com/repos/citation-style-language/locales/contents/\?ref\=master | \ sed -e '/"name":.*\.xml"/{s/\s*"name": "//;s/\.xml",\s*//;s/locales-//;p};d' exit 0 @@ -108,7 +109,10 @@ for doi in "$@"; do # we do some encoding as needed. doi=${doi//+/%2B} http_headers="Accept: text/x-bibliography; style=${style}; locale=${locale}" + + echo "$http_headers" + echo # we retrieve the data from https://doi.org/ - curl -s -LH "$http_headers" "https://doi.org/${doi}" + curl -LH "$http_headers" "https://doi.org/${doi}" done From a30d461954217a0cef3d07509b6726d9a092eacd Mon Sep 17 00:00:00 2001 From: "Felipe S. S. Schneider" Date: Fri, 28 Apr 2023 11:38:31 -0300 Subject: [PATCH 02/29] feat: add an old Python rewrite idea --- src/bibmagic.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/bibmagic.py diff --git a/src/bibmagic.py b/src/bibmagic.py new file mode 100644 index 0000000..7935507 --- /dev/null +++ b/src/bibmagic.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python + +import sys +import bibtexparser as btp +from bibtexparser.bparser import BibTexParser +from bibtexparser.customization import homogenize_latex_encoding + +with open(sys.argv[1]) as bib_file: + parser = BibTexParser() + # parser.customization = homogenize_latex_encoding + bib_db = btp.load(bib_file, parser=parser) + +if len(sys.argv) > 2: + with open(sys.argv[2], 'w') as bib_file: + btp.dump(bib_db, bib_file) +else: + print(btp.dumps(bib_db)) From 6f30430cb6f6321d719c3d6feac7120b6f94f0ce Mon Sep 17 00:00:00 2001 From: "Felipe S. S. Schneider" Date: Fri, 28 Apr 2023 11:42:01 -0300 Subject: [PATCH 03/29] feat: start a rewrite --- .gitignore | 20 ++++++++++++++++++++ Cargo.toml | 8 ++++++++ src/main.rs | 3 +++ 3 files changed, 31 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..389159a --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +# Created by https://www.toptal.com/developers/gitignore/api/rust +# Edit at https://www.toptal.com/developers/gitignore?templates=rust + +### Rust ### +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +# End of https://www.toptal.com/developers/gitignore/api/rust diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..7796343 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "doi2bib" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} From 1d5ac087402565af0d19a76468abd4a909def393 Mon Sep 17 00:00:00 2001 From: "Felipe S. S. Schneider" Date: Fri, 28 Apr 2023 11:42:48 -0300 Subject: [PATCH 04/29] feat: move to the right place --- src/{main.rs => bin/doi2bib.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{main.rs => bin/doi2bib.rs} (100%) diff --git a/src/main.rs b/src/bin/doi2bib.rs similarity index 100% rename from src/main.rs rename to src/bin/doi2bib.rs From 9302fa9085101e0b5201a68b02b03e808a1f9b18 Mon Sep 17 00:00:00 2001 From: "Felipe S. S. Schneider" Date: Fri, 28 Apr 2023 11:44:27 -0300 Subject: [PATCH 05/29] feat: add the other rewrites --- src/bin/doi2text.rs | 3 +++ src/bin/url2bib.rs | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 src/bin/doi2text.rs create mode 100644 src/bin/url2bib.rs diff --git a/src/bin/doi2text.rs b/src/bin/doi2text.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/src/bin/doi2text.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/src/bin/url2bib.rs b/src/bin/url2bib.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/src/bin/url2bib.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} From 410928805881f554e549ae5591587697addd2a82 Mon Sep 17 00:00:00 2001 From: "Felipe S. S. Schneider" Date: Fri, 28 Apr 2023 11:58:07 -0300 Subject: [PATCH 06/29] feat: rewrite doi2bib with the help of ChatGPT --- Cargo.toml | 1 + src/bin/doi2bib.rs | 78 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 7796343..adbb3ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,4 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +reqwest = { version = "0.11", features = ["blocking"] } diff --git a/src/bin/doi2bib.rs b/src/bin/doi2bib.rs index e7a11a9..8ba9c74 100644 --- a/src/bin/doi2bib.rs +++ b/src/bin/doi2bib.rs @@ -1,3 +1,79 @@ +use std::env; +use std::process; + +fn print_usage(program: &str) { + println!("Usage: {} doi [doi...]", program); + println!("\t-h show this message"); + println!("\t-v show version"); +} + +fn print_version() { + println!("doi2bib 1.1"); +} + fn main() { - println!("Hello, world!"); + let args: Vec = env::args().collect(); + let program = args[0].clone(); + + let mut do_help = false; + let mut do_version = false; + + let mut doi_list = Vec::new(); + + let mut i = 1; + while i < args.len() { + match args[i].as_str() { + "-h" => do_help = true, + "-v" => do_version = true, + _ => doi_list.push(args[i].clone()), + } + i += 1; + } + + if do_help { + print_usage(&program); + return; + } + + if do_version { + print_version(); + return; + } + + if doi_list.is_empty() { + eprintln!("No DOI provided"); + print_usage(&program); + process::exit(1); + } + + let http_headers = "Accept: text/bibliography; style=bibtex"; + + for doi in doi_list { + // We do some encoding as needed. + let doi = doi.replace("+", "%2B"); + println!("{}", http_headers); + println!(); + + // We retrieve the data from https://doi.org/ + match reqwest::blocking::Client::new() + .get(&format!("https://doi.org/{}", doi)) + .header("Accept", http_headers) + .send() + { + Ok(resp) => { + let bibtex_data = resp + .text() + .unwrap() + .replace(", ", ",\n ") + .replace("}, ", "},\n ") + .replace(",\n ", ", ") + .replace("}}", "}\n}\n"); + println!("{}", bibtex_data); + } + Err(e) => { + eprintln!("Error retrieving DOI {}: {}", doi, e); + process::exit(1); + } + } + } } From 77cee73bbad5dfba776de35af8cb26b34e1e77c9 Mon Sep 17 00:00:00 2001 From: "Felipe S. S. Schneider" Date: Fri, 28 Apr 2023 11:58:47 -0300 Subject: [PATCH 07/29] style: happy Clippy --- src/bin/doi2bib.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/bin/doi2bib.rs b/src/bin/doi2bib.rs index 8ba9c74..505fa70 100644 --- a/src/bin/doi2bib.rs +++ b/src/bin/doi2bib.rs @@ -2,7 +2,7 @@ use std::env; use std::process; fn print_usage(program: &str) { - println!("Usage: {} doi [doi...]", program); + println!("Usage: {program} doi [doi...]"); println!("\t-h show this message"); println!("\t-v show version"); } @@ -50,13 +50,13 @@ fn main() { for doi in doi_list { // We do some encoding as needed. - let doi = doi.replace("+", "%2B"); - println!("{}", http_headers); + let doi = doi.replace('+', "%2B"); + println!("{http_headers}"); println!(); // We retrieve the data from https://doi.org/ match reqwest::blocking::Client::new() - .get(&format!("https://doi.org/{}", doi)) + .get(&format!("https://doi.org/{doi}")) .header("Accept", http_headers) .send() { @@ -68,10 +68,10 @@ fn main() { .replace("}, ", "},\n ") .replace(",\n ", ", ") .replace("}}", "}\n}\n"); - println!("{}", bibtex_data); + println!("{bibtex_data}"); } Err(e) => { - eprintln!("Error retrieving DOI {}: {}", doi, e); + eprintln!("Error retrieving DOI {doi}: {e}"); process::exit(1); } } From 0020dea326420f9fe98607df7c655763124816a0 Mon Sep 17 00:00:00 2001 From: "Felipe S. S. Schneider" Date: Fri, 28 Apr 2023 12:03:59 -0300 Subject: [PATCH 08/29] fix: headers --- src/bin/doi2bib.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/bin/doi2bib.rs b/src/bin/doi2bib.rs index 505fa70..9454528 100644 --- a/src/bin/doi2bib.rs +++ b/src/bin/doi2bib.rs @@ -46,18 +46,14 @@ fn main() { process::exit(1); } - let http_headers = "Accept: text/bibliography; style=bibtex"; - for doi in doi_list { // We do some encoding as needed. let doi = doi.replace('+', "%2B"); - println!("{http_headers}"); - println!(); // We retrieve the data from https://doi.org/ match reqwest::blocking::Client::new() .get(&format!("https://doi.org/{doi}")) - .header("Accept", http_headers) + .header(reqwest::header::ACCEPT, "text/bibliography; style=bibtex") .send() { Ok(resp) => { From d0c9749c2ff9f88ac04a6615f828635a47c191c5 Mon Sep 17 00:00:00 2001 From: "Felipe S. S. Schneider" Date: Fri, 28 Apr 2023 12:09:56 -0300 Subject: [PATCH 09/29] feat: start rewriting using clap --- Cargo.toml | 1 + src/bin/doi2bib.rs | 60 ++++++++++++++++------------------------------ 2 files changed, 21 insertions(+), 40 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index adbb3ae..91fe093 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,4 +6,5 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +clap = "4.2.5" reqwest = { version = "0.11", features = ["blocking"] } diff --git a/src/bin/doi2bib.rs b/src/bin/doi2bib.rs index 9454528..de26851 100644 --- a/src/bin/doi2bib.rs +++ b/src/bin/doi2bib.rs @@ -1,58 +1,38 @@ -use std::env; use std::process; -fn print_usage(program: &str) { - println!("Usage: {program} doi [doi...]"); - println!("\t-h show this message"); - println!("\t-v show version"); -} +use clap::App; +use clap::AppSettings; +use clap::Arg; fn print_version() { println!("doi2bib 1.1"); } fn main() { - let args: Vec = env::args().collect(); - let program = args[0].clone(); - - let mut do_help = false; - let mut do_version = false; - - let mut doi_list = Vec::new(); - - let mut i = 1; - while i < args.len() { - match args[i].as_str() { - "-h" => do_help = true, - "-v" => do_version = true, - _ => doi_list.push(args[i].clone()), - } - i += 1; - } - - if do_help { - print_usage(&program); - return; - } - - if do_version { + let matches = App::new("doi2bib") + .version("1.1") + .setting(AppSettings::ArgRequiredElseHelp) + .about("Retrieves BibTeX data for a given DOI") + .arg( + Arg::with_name("doi") + .multiple(true) + .required(true) + .help("The DOI to retrieve BibTeX data for"), + ) + .get_matches(); + + if matches.is_present("version") { print_version(); return; } - if doi_list.is_empty() { - eprintln!("No DOI provided"); - print_usage(&program); - process::exit(1); - } - - for doi in doi_list { + for doi in matches.values_of("doi").unwrap() { // We do some encoding as needed. let doi = doi.replace('+', "%2B"); // We retrieve the data from https://doi.org/ match reqwest::blocking::Client::new() - .get(&format!("https://doi.org/{doi}")) + .get(&format!("https://doi.org/{}", doi)) .header(reqwest::header::ACCEPT, "text/bibliography; style=bibtex") .send() { @@ -64,10 +44,10 @@ fn main() { .replace("}, ", "},\n ") .replace(",\n ", ", ") .replace("}}", "}\n}\n"); - println!("{bibtex_data}"); + println!("{}", bibtex_data); } Err(e) => { - eprintln!("Error retrieving DOI {doi}: {e}"); + eprintln!("Error retrieving DOI {}: {}", doi, e); process::exit(1); } } From 482315599b5c97ce955aee13edc1dbf55eb3e774 Mon Sep 17 00:00:00 2001 From: "Felipe S. S. Schneider" Date: Fri, 28 Apr 2023 12:17:43 -0300 Subject: [PATCH 10/29] feat: use clap derive API --- Cargo.toml | 2 +- src/bin/doi2bib.rs | 39 ++++++++++++++------------------------- 2 files changed, 15 insertions(+), 26 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 91fe093..44339e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,5 +6,5 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -clap = "4.2.5" +clap = { version = "4.2.5", features = ["derive"] } reqwest = { version = "0.11", features = ["blocking"] } diff --git a/src/bin/doi2bib.rs b/src/bin/doi2bib.rs index de26851..56eb810 100644 --- a/src/bin/doi2bib.rs +++ b/src/bin/doi2bib.rs @@ -1,38 +1,27 @@ use std::process; -use clap::App; -use clap::AppSettings; -use clap::Arg; +use clap::Parser; -fn print_version() { - println!("doi2bib 1.1"); +/// Retrieve BibTeX data for a given DOI. +#[derive(Parser)] +#[command(author, version, about, arg_required_else_help(true))] +struct Cli { + /// DOI to retrieve BibTeX data for. + doi: Vec, } -fn main() { - let matches = App::new("doi2bib") - .version("1.1") - .setting(AppSettings::ArgRequiredElseHelp) - .about("Retrieves BibTeX data for a given DOI") - .arg( - Arg::with_name("doi") - .multiple(true) - .required(true) - .help("The DOI to retrieve BibTeX data for"), - ) - .get_matches(); +type Doi = String; - if matches.is_present("version") { - print_version(); - return; - } +fn main() { + let cli = Cli::parse(); - for doi in matches.values_of("doi").unwrap() { + for doi in cli.doi { // We do some encoding as needed. let doi = doi.replace('+', "%2B"); // We retrieve the data from https://doi.org/ match reqwest::blocking::Client::new() - .get(&format!("https://doi.org/{}", doi)) + .get(&format!("https://doi.org/{doi}")) .header(reqwest::header::ACCEPT, "text/bibliography; style=bibtex") .send() { @@ -44,10 +33,10 @@ fn main() { .replace("}, ", "},\n ") .replace(",\n ", ", ") .replace("}}", "}\n}\n"); - println!("{}", bibtex_data); + println!("{bibtex_data}"); } Err(e) => { - eprintln!("Error retrieving DOI {}: {}", doi, e); + eprintln!("Error retrieving DOI {doi}: {e}"); process::exit(1); } } From 814ea5f2e3d35545789efa4bce709c6e2fc27a38 Mon Sep 17 00:00:00 2001 From: "Felipe S. S. Schneider" Date: Fri, 28 Apr 2023 12:24:57 -0300 Subject: [PATCH 11/29] refactor: simplify and delegate --- src/bin/doi2bib.rs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/bin/doi2bib.rs b/src/bin/doi2bib.rs index 56eb810..d94bd57 100644 --- a/src/bin/doi2bib.rs +++ b/src/bin/doi2bib.rs @@ -16,23 +16,13 @@ fn main() { let cli = Cli::parse(); for doi in cli.doi { - // We do some encoding as needed. - let doi = doi.replace('+', "%2B"); - - // We retrieve the data from https://doi.org/ match reqwest::blocking::Client::new() .get(&format!("https://doi.org/{doi}")) .header(reqwest::header::ACCEPT, "text/bibliography; style=bibtex") .send() + .and_then(reqwest::blocking::Response::text) { - Ok(resp) => { - let bibtex_data = resp - .text() - .unwrap() - .replace(", ", ",\n ") - .replace("}, ", "},\n ") - .replace(",\n ", ", ") - .replace("}}", "}\n}\n"); + Ok(bibtex_data) => { println!("{bibtex_data}"); } Err(e) => { From a754fc8849f41a624be77e1a577609bb19b648ed Mon Sep 17 00:00:00 2001 From: "Felipe S. S. Schneider" Date: Fri, 28 Apr 2023 12:35:51 -0300 Subject: [PATCH 12/29] refactor: import stuff --- src/bin/doi2bib.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/bin/doi2bib.rs b/src/bin/doi2bib.rs index d94bd57..085db06 100644 --- a/src/bin/doi2bib.rs +++ b/src/bin/doi2bib.rs @@ -1,6 +1,9 @@ use std::process; use clap::Parser; +use reqwest::blocking::Client; +use reqwest::blocking::Response; +use reqwest::header; /// Retrieve BibTeX data for a given DOI. #[derive(Parser)] @@ -16,11 +19,11 @@ fn main() { let cli = Cli::parse(); for doi in cli.doi { - match reqwest::blocking::Client::new() + match Client::new() .get(&format!("https://doi.org/{doi}")) - .header(reqwest::header::ACCEPT, "text/bibliography; style=bibtex") + .header(header::ACCEPT, "text/bibliography; style=bibtex") .send() - .and_then(reqwest::blocking::Response::text) + .and_then(Response::text) { Ok(bibtex_data) => { println!("{bibtex_data}"); From 5aa99f15119e8aabc301f53ffb8cf94d96a12de8 Mon Sep 17 00:00:00 2001 From: "Felipe S. S. Schneider" Date: Fri, 28 Apr 2023 14:13:53 -0300 Subject: [PATCH 13/29] refactor: make things strongly typed --- Cargo.toml | 3 ++ src/bin/doi2bib.rs | 88 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 89 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 44339e8..0565524 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,4 +7,7 @@ edition = "2021" [dependencies] clap = { version = "4.2.5", features = ["derive"] } +itertools = "0.10.5" reqwest = { version = "0.11", features = ["blocking"] } +thiserror = "1.0.40" +url = "2.3.1" diff --git a/src/bin/doi2bib.rs b/src/bin/doi2bib.rs index 085db06..07b1c7e 100644 --- a/src/bin/doi2bib.rs +++ b/src/bin/doi2bib.rs @@ -1,26 +1,110 @@ +use std::fmt; use std::process; +use std::str::FromStr; use clap::Parser; +use itertools::Itertools; use reqwest::blocking::Client; use reqwest::blocking::Response; use reqwest::header; +use thiserror::Error; +use url::Url; /// Retrieve BibTeX data for a given DOI. #[derive(Parser)] #[command(author, version, about, arg_required_else_help(true))] struct Cli { /// DOI to retrieve BibTeX data for. + #[arg(value_parser(Doi::from_str))] doi: Vec, } -type Doi = String; +#[derive(Clone)] +struct Doi { + prefix: DoiPrefix, + suffix: DoiSuffix, +} + +#[derive(Debug, Error)] +enum DoiParseError { + #[error("slash not found")] + SlashNotFound, + + #[error("not in DOI namespace")] + NotInDoiNamespace, +} + +impl FromStr for Doi { + type Err = DoiParseError; + + fn from_str(s: &str) -> Result { + let s = s.strip_prefix(DOI_ORG).unwrap_or(s); + let s = s.strip_prefix("doi:").unwrap_or(s); + + let (prefix, suffix) = s.split_once('/').ok_or(DoiParseError::SlashNotFound)?; + Ok(Self { + prefix: prefix.parse()?, + suffix: suffix.to_lowercase(), + }) + } +} + +#[derive(Clone)] +struct DoiPrefix { + subdivisions: Vec, +} + +impl FromStr for DoiPrefix { + type Err = DoiParseError; + + fn from_str(s: &str) -> Result { + let s = s + .strip_prefix("10.") + .ok_or(DoiParseError::NotInDoiNamespace)?; + let subdivisions = s.split('.').map(str::to_lowercase).collect(); + Ok(Self { subdivisions }) + } +} + +type DoiSuffix = String; + +impl fmt::Display for Doi { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}/{}", self.prefix, self.suffix) + } +} + +impl fmt::Display for DoiPrefix { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.subdivisions + .iter() + .map(std::string::String::as_str) + .intersperse(".") + .try_for_each(|s| write!(f, "{s}")) + } +} + +const DOI_ORG: &str = "https://doi.org/"; + +impl From for Url { + fn from(doi: Doi) -> Self { + Self::parse(&format!("{DOI_ORG}{doi}")).expect("DOI should always be translatable to URL") + } +} + +impl From<&Doi> for Url { + fn from(doi: &Doi) -> Self { + Self::parse(&format!("https://doi.org/{doi}")) + .expect("DOI should always be translatable to URL") + } +} fn main() { let cli = Cli::parse(); for doi in cli.doi { match Client::new() - .get(&format!("https://doi.org/{doi}")) + .get(Url::from(&doi)) .header(header::ACCEPT, "text/bibliography; style=bibtex") .send() .and_then(Response::text) From 1baae7027564b1204b50f81d1c23b780601b1401 Mon Sep 17 00:00:00 2001 From: "Felipe S. S. Schneider" Date: Fri, 28 Apr 2023 14:30:43 -0300 Subject: [PATCH 14/29] refactor: use nutype --- Cargo.toml | 1 + src/bin/doi2bib.rs | 46 ++++++++++++++++++++++++++++++++++------------ 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0565524..6c90a88 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" [dependencies] clap = { version = "4.2.5", features = ["derive"] } itertools = "0.10.5" +nutype = "0.2.0" reqwest = { version = "0.11", features = ["blocking"] } thiserror = "1.0.40" url = "2.3.1" diff --git a/src/bin/doi2bib.rs b/src/bin/doi2bib.rs index 07b1c7e..504f1ed 100644 --- a/src/bin/doi2bib.rs +++ b/src/bin/doi2bib.rs @@ -4,6 +4,7 @@ use std::str::FromStr; use clap::Parser; use itertools::Itertools; +use nutype::nutype; use reqwest::blocking::Client; use reqwest::blocking::Response; use reqwest::header; @@ -11,7 +12,7 @@ use thiserror::Error; use url::Url; /// Retrieve BibTeX data for a given DOI. -#[derive(Parser)] +#[derive(Clone, Debug, Parser)] #[command(author, version, about, arg_required_else_help(true))] struct Cli { /// DOI to retrieve BibTeX data for. @@ -19,19 +20,25 @@ struct Cli { doi: Vec, } -#[derive(Clone)] +#[derive(Clone, Debug)] struct Doi { prefix: DoiPrefix, suffix: DoiSuffix, } -#[derive(Debug, Error)] +#[derive(Clone, Debug, Error)] enum DoiParseError { #[error("slash not found")] SlashNotFound, #[error("not in DOI namespace")] - NotInDoiNamespace, + NotInNamespace, + + #[error("failed to parse prefix subdivision: {0}")] + PrefixSubdivision(#[from] DoiPrefixSubdivisionError), + + #[error("failed to parse suffix: {0}")] + Suffix(#[from] DoiSuffixError), } impl FromStr for Doi { @@ -44,29 +51,42 @@ impl FromStr for Doi { let (prefix, suffix) = s.split_once('/').ok_or(DoiParseError::SlashNotFound)?; Ok(Self { prefix: prefix.parse()?, - suffix: suffix.to_lowercase(), + suffix: suffix.parse()?, }) } } -#[derive(Clone)] +#[derive(Clone, Debug)] struct DoiPrefix { - subdivisions: Vec, + subdivisions: Vec, } +#[nutype( + sanitize(trim, lowercase) + validate(not_empty) +)] +#[derive(AsRef, Clone, Debug, Display, FromStr)] +struct DoiPrefixSubdivision(String); + impl FromStr for DoiPrefix { type Err = DoiParseError; fn from_str(s: &str) -> Result { let s = s - .strip_prefix("10.") - .ok_or(DoiParseError::NotInDoiNamespace)?; - let subdivisions = s.split('.').map(str::to_lowercase).collect(); + .strip_prefix(DOI_NAMESPACE) + .ok_or(DoiParseError::NotInNamespace)?; + let subdivisions: Result<_, _> = s.split('.').map(|s| s.parse()).collect(); + let subdivisions = subdivisions?; Ok(Self { subdivisions }) } } -type DoiSuffix = String; +#[nutype( + sanitize(trim, lowercase) + validate(not_empty) +)] +#[derive(AsRef, Clone, Debug, Display, FromStr)] +struct DoiSuffix(String); impl fmt::Display for Doi { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -76,14 +96,16 @@ impl fmt::Display for Doi { impl fmt::Display for DoiPrefix { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{DOI_NAMESPACE}")?; self.subdivisions .iter() - .map(std::string::String::as_str) + .map(|s| s.as_ref()) .intersperse(".") .try_for_each(|s| write!(f, "{s}")) } } +const DOI_NAMESPACE: &str = "10."; const DOI_ORG: &str = "https://doi.org/"; impl From for Url { From 358146b2c1fa4da9fb3b18203bba905e208f30a6 Mon Sep 17 00:00:00 2001 From: "Felipe S. S. Schneider" Date: Fri, 28 Apr 2023 14:31:41 -0300 Subject: [PATCH 15/29] style: happy Clippy --- src/bin/doi2bib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bin/doi2bib.rs b/src/bin/doi2bib.rs index 504f1ed..1e569d5 100644 --- a/src/bin/doi2bib.rs +++ b/src/bin/doi2bib.rs @@ -75,7 +75,7 @@ impl FromStr for DoiPrefix { let s = s .strip_prefix(DOI_NAMESPACE) .ok_or(DoiParseError::NotInNamespace)?; - let subdivisions: Result<_, _> = s.split('.').map(|s| s.parse()).collect(); + let subdivisions: Result<_, _> = s.split('.').map(str::parse).collect(); let subdivisions = subdivisions?; Ok(Self { subdivisions }) } @@ -99,7 +99,7 @@ impl fmt::Display for DoiPrefix { write!(f, "{DOI_NAMESPACE}")?; self.subdivisions .iter() - .map(|s| s.as_ref()) + .map(AsRef::as_ref) .intersperse(".") .try_for_each(|s| write!(f, "{s}")) } From 2b9043a065fdc8fdb59091b105f879ff6fb820d3 Mon Sep 17 00:00:00 2001 From: "Felipe S. S. Schneider" Date: Fri, 28 Apr 2023 14:38:52 -0300 Subject: [PATCH 16/29] chore: rename and reformat --- Cargo.toml | 2 +- README.md | 10 +- cast.svg | 15058 ++++++++++++++++++++++++++++++++++++++++++++++++- src/doi2bib | 20 +- src/doi2text | 26 +- src/url2bib | 24 +- 6 files changed, 15098 insertions(+), 42 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6c90a88..162b729 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "doi2bib" +name = "doi" version = "0.1.0" edition = "2021" diff --git a/README.md b/README.md index 7648661..9d24388 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ By default, it will install in `/usr/local/bin/`. pages={3792–3799} } - $ url2bib google.com http://schneiderfelipe.xyz/ https://github.com/schneiderfelipe/doi2bib + $ url2bib google.com https://schneiderfelipe.github.io/ https://github.com/schneiderfelipe/doi @misc{google, title = {Google}, howpublished = {\url{google.com}}, @@ -87,13 +87,13 @@ By default, it will install in `/usr/local/bin/`. @misc{felipe-s-s-schneider, title = {Felipe S. S. Schneider}, - howpublished = {\url{http://schneiderfelipe.xyz/}}, + howpublished = {\url{https://schneiderfelipe.github.io/}}, note = {Accessed: 2020-01-27} } - @misc{github-schneiderfelipe-doi2bib-convert-dois-and-urls-to-bibtex, - title = {GitHub - schneiderfelipe/doi2bib: Convert DOIs and URLs to BibTeX}, - howpublished = {\url{https://github.com/schneiderfelipe/doi2bib}}, + @misc{github-schneiderfelipe-doi-convert-dois-and-urls-to-bibtex, + title = {GitHub - schneiderfelipe/doi: Convert DOIs and URLs to BibTeX}, + howpublished = {\url{https://github.com/schneiderfelipe/doi}}, note = {Accessed: 2020-01-27} } diff --git a/cast.svg b/cast.svg index eb16f27..c7a2c26 100644 --- a/cast.svg +++ b/cast.svg @@ -1 +1,15057 @@ -~doi2text-len-GB10.1021/acs.jpcc.8b06244#allchicagos~~doi2bibhttps://doi.org/10.1021/acs.jpcc.8b06244@article{Schneider_2018,title={HowDoSecondaryPhosphineOxidesInteractwithSilverNanoclusters?InsightsfromComputation},volume={122},ISSN={1932-7455},url={http://dx.doi.org/10.1021/acs.jpcc.8b06244},DOI={10.1021/acs.jpcc.8b06244},number={37},journal={TheJournalofPhysicalChemistryC},publisher={AmericanChemicalSociety(ACS)},author={Schneider,FelipeS.S.andSegala,MaximilianoandCaramori,GiovanniF.anddaSilva,EderHenriqueandParreira,RenatoL.T.andSchrekker,HenriS.andvanLeeuwen,PietW.N.M.},year={2018},month={Aug},pages={21449–21461}}~doi2bib10.1021/acs.jpcc.8b06244~doi2text10.1021/acs.jpcc.8b06244Schneider,FelipeS.S.,MaximilianoSegala,GiovanniF.Caramori,EderHenriquedaSilva,RenatoL.T.Parreira,HenriS.Schrekker,ePietW.N.M.vanLeeuwen.2018.“HowDoSecondaryPhosphineOxidesInteractwithSilverNanoclusters?InsightsfromComputation”.TheJournalofPhysicalChemistryC122(37)(agosto27):21449–21461.doi:10.1021/acs.jpcc.8b06244.http://dx.doi.org/10.1021/acs.jpcc.8b06244.~doi2text-l10.1021/acs.jpcc.8b06244~doi2text-lde-DE10.1021/acs.jpcc.8b06244daSilva,RenatoL.T.Parreira,HenriS.Schrekker,undPietW.N.M.vanLeeuwen.2018.„HowDoSecondaryPhosphineOxidesInteractwithSilverNanoclusters?InsightsfromComputation“.TheJournalofPhysicalChemistryC122(37)(August27):21449–21461.doi:10.1021/acs.jpcc.8b06244.http://dx.doi.org/10.1021/acs.jpcc.8b06244.~doi2text-l10.1021/acs.jpcc.8b06244~doi2text-le10.1021/acs.jpcc.8b06244~doi2text-len10.1021/acs.jpcc.8b06244~doi2text-len-10.1021/acs.jpcc.8b06244~doi2text-len-G10.1021/acs.jpcc.8b06244~doi2text-len-GB10.1021/acs.jpcc.8b06244~doi2text-len-GB10.1021/acs.jpcc.8b06244#~doi2text-len-GB10.1021/acs.jpcc.8b06244#a~doi2text-len-GB10.1021/acs.jpcc.8b06244#al~doi2text-len-GB10.1021/acs.jpcc.8b06244#all~doi2text-len-GB10.1021/acs.jpcc.8b06244#allc~doi2text-len-GB10.1021/acs.jpcc.8b06244#allch~doi2text-len-GB10.1021/acs.jpcc.8b06244#allchi~doi2text-len-GB10.1021/acs.jpcc.8b06244#allchic~doi2text-len-GB10.1021/acs.jpcc.8b06244#allchica~doi2text-len-GB10.1021/acs.jpcc.8b06244#allchicag~doi2text-len-GB10.1021/acs.jpcc.8b06244#allchicago~doi2bib~doi2text-len-GB10.1021/acs.jpcc.8b06244#allchicagost~doi2text-len-GB10.1021/acs.jpcc.8b06244#allchicagosty~doi2text-len-GB10.1021/acs.jpcc.8b06244#allchicagostyl~doi2text-len-GB10.1021/acs.jpcc.8b06244#allchicagostyle~doi2text-len-GB10.1021/acs.jpcc.8b06244#allchicagostyleb~doi2text-len-GB10.1021/acs.jpcc.8b06244#allchicagostyleby~doi2text-len-GB10.1021/acs.jpcc.8b06244#allchicagostylebyd~doi2text-len-GB10.1021/acs.jpcc.8b06244#allchicagostylebyde~doi2text-len-GB10.1021/acs.jpcc.8b06244#allchicagostylebydef~doi2text-len-GB10.1021/acs.jpcc.8b06244#allchicagostylebydefa~doi2text-len-GB10.1021/acs.jpcc.8b06244#allchicagostylebydefau~doi2text-len-GB10.1021/acs.jpcc.8b06244#allchicagostylebydefaul~doi2text-len-GB10.1021/acs.jpcc.8b06244#allchicagostylebydefaultdaSilva,RenatoL.T.Parreira,HenriS.Schrekker,andPietW.N.M.vanLeeuwen.2018.‘HowDoSecondaryPhosphineOxidesInteractwithSilverNanoclusters?InsightsfromComputation’.TheJournalofPhysicalChemistryC122(37)(Augus~doi2text-s10.1021/acs.jpcc.8b06244~doi2text-sharvard310.1021/acs.jpcc.8b06244Schneider,F.S.S.etal.,2018.HowDoSecondaryPhosphineOxidesInteractwithSilverNanoclusters?InsightsfromComputation.TheJournalofPhysicalChemistryC,122(37),p.21449–21461.Availableat:http://dx.doi.org/10.1021/acs.jpcc.8b06244.~doi2text-samerican-chemical-society10.1021/acs.jpcc.8b06244(1)Schneider,F.S.S.;Segala,M.;Caramori,G.F.;daSilva,E.H.;Parreira,R.L.T.;Schrekker,H.S.;vanLeeuwen,P.W.N.M.TheJournalofPhysicalChemistryC2018,122,21449–21461.~doi2text-samerican-chemical-society10.1021/acs.jpcc.8b0624410.1103/PhysRev.140.A113310.1021/la203078wRev.140.A113310.1021/la203078w#Rev.140.A113310.1021/la203078w#asRev.140.A113310.1021/la203078w#asmanyRev.140.A113310.1021/la203078w#asmanyasRev.140.A113310.1021/la203078w#asmanyasyouRev.140.A113310.1021/la203078w#asmanyasyouwant(1)Kohn,W.;Sham,L.J.PhysicalReview1965,140,A1133–A1138.(1)Cabaleiro-Lago,C.;Szczepankiewicz,O.;Linse,S.Langmuir2012,28,1852–1857.~url2bib~url2bibhttps://github.com/schneiderfelipe/doi2bib@misc{github-schneiderfelipe-doi2bib-easily-convert-digital-object-identifier-doi-and-uniform-resource-locator-url-to-bibtex,title={GitHub-schneiderfelipe/doi2bib:EasilyconvertDigitalObjectIdentifier(DOI)andUniformResourceLocator(URL)toBibTeX.},howpublished={\url{https://github.com/schneiderfelipe/doi2bib}},note={Accessed:2020-09-22}~#~#have~#havefun~#havefun:)~~d~do~doi~doi2~doi2b~doi2bi~doi2bibhttps://doi.org/10.1021/acs.jpcc.8b06244~doi2bibhttps://doi.org10.1021/acs.jpcc.8b06244~doi2bibhttps://doi.or10.1021/acs.jpcc.8b06244~doi2bibhttps://doi.o10.1021/acs.jpcc.8b06244~doi2bibhttps://doi.10.1021/acs.jpcc.8b06244~doi2bibhttps://doi10.1021/acs.jpcc.8b06244~doi2bibhttps://do10.1021/acs.jpcc.8b06244~doi2bibhttps://d10.1021/acs.jpcc.8b06244~doi2bibhttps://10.1021/acs.jpcc.8b06244~doi2bibhttps:/10.1021/acs.jpcc.8b06244~doi2bibhttps:10.1021/acs.jpcc.8b06244~doi2bibhttps10.1021/acs.jpcc.8b06244~doi2bibhttp10.1021/acs.jpcc.8b06244~doi2bibhtt10.1021/acs.jpcc.8b06244~doi2bibht10.1021/acs.jpcc.8b06244~doi2bibh10.1021/acs.jpcc.8b06244~doi2bib10.1021/acs.jpcc.8b06244~doi2bi10.1021/acs.jpcc.8b06244~doi2b10.1021/acs.jpcc.8b06244~doi210.1021/acs.jpcc.8b06244~doi2t10.1021/acs.jpcc.8b06244~doi2te10.1021/acs.jpcc.8b06244~doi2tex10.1021/acs.jpcc.8b06244~doi2text10.1021/acs.jpcc.8b06244~doi2text-10.1021/acs.jpcc.8b06244~doi2text-l10.1021/acs.jpcc.8b06244~doi2text-ld10.1021/acs.jpcc.8b06244~doi2text-lde10.1021/acs.jpcc.8b06244~doi2text-lde-10.1021/acs.jpcc.8b06244~doi2text-lde-D10.1021/acs.jpcc.8b06244~doi2text-lde-DE10.1021/acs.jpcc.8b06244~doi2text-le-DE10.1021/acs.jpcc.8b06244~doi2text-l-DE10.1021/acs.jpcc.8b06244~doi2text-lDE10.1021/acs.jpcc.8b06244~doi2text-lE10.1021/acs.jpcc.8b06244~doi2text-10.1021/acs.jpcc.8b06244~doi2text-s10.1021/acs.jpcc.8b06244~doi2text-sh10.1021/acs.jpcc.8b06244~doi2text-sha10.1021/acs.jpcc.8b06244~doi2text-shar10.1021/acs.jpcc.8b06244~doi2text-sharv10.1021/acs.jpcc.8b06244~doi2text-sharva10.1021/acs.jpcc.8b06244~doi2text-sharvar10.1021/acs.jpcc.8b06244~doi2text-sharvard10.1021/acs.jpcc.8b06244~doi2text-sarvard310.1021/acs.jpcc.8b06244~doi2text-srvard310.1021/acs.jpcc.8b06244~doi2text-svard310.1021/acs.jpcc.8b06244~doi2text-sard310.1021/acs.jpcc.8b06244~doi2text-srd310.1021/acs.jpcc.8b06244~doi2text-sd310.1021/acs.jpcc.8b06244~doi2text-s310.1021/acs.jpcc.8b06244~doi2text-sa10.1021/acs.jpcc.8b06244~doi2text-sam10.1021/acs.jpcc.8b06244~doi2text-same10.1021/acs.jpcc.8b06244~doi2text-samer10.1021/acs.jpcc.8b06244~doi2text-sameri10.1021/acs.jpcc.8b06244~doi2text-sameric10.1021/acs.jpcc.8b06244~doi2text-samerica10.1021/acs.jpcc.8b06244~doi2text-samerican10.1021/acs.jpcc.8b06244~doi2text-samerican-10.1021/acs.jpcc.8b06244~doi2text-samerican-c10.1021/acs.jpcc.8b06244~doi2text-samerican-ch10.1021/acs.jpcc.8b06244~doi2text-samerican-che10.1021/acs.jpcc.8b06244~doi2text-samerican-chem10.1021/acs.jpcc.8b06244~doi2text-samerican-chemi10.1021/acs.jpcc.8b06244~doi2text-samerican-chemic10.1021/acs.jpcc.8b06244~doi2text-samerican-chemica10.1021/acs.jpcc.8b06244~doi2text-samerican-chemical10.1021/acs.jpcc.8b06244~doi2text-samerican-chemical-10.1021/acs.jpcc.8b06244~doi2text-samerican-chemical-s10.1021/acs.jpcc.8b06244~doi2text-samerican-chemical-so10.1021/acs.jpcc.8b06244~doi2text-samerican-chemical-soc10.1021/acs.jpcc.8b06244~doi2text-samerican-chemical-soci10.1021/acs.jpcc.8b06244~doi2text-samerican-chemical-socie10.1021/acs.jpcc.8b06244~doi2text-samerican-chemical-societ10.1021/acs.jpcc.8b06244~doi2text-samerican-chemical-society10.1021/acs.jpcc.8b0624410.1103/PhysRev.140.A1133Rev.140.A1133Rev.140.A113310.1021/la203078wRev.140.A113310.1021/la203078w#aRev.140.A113310.1021/la203078w#asmRev.140.A113310.1021/la203078w#asmaRev.140.A113310.1021/la203078w#asmanRev.140.A113310.1021/la203078w#asmanyaRev.140.A113310.1021/la203078w#asmanyasyRev.140.A113310.1021/la203078w#asmanyasyoRev.140.A113310.1021/la203078w#asmanyasyouwRev.140.A113310.1021/la203078w#asmanyasyouwaRev.140.A113310.1021/la203078w#asmanyasyouwan~u~ur~url~url2~url2b~url2bi~url2bibhttps://github.com/schneiderfelipe/doi2bib~#h~#ha~#hav~#havef~#havefu~#havefun: \ No newline at end of file +~doi2text-len-GB10.1021/acs.jpcc.8b06244#allchicagos~~doi2bibhttps://doi.org/10.1021/acs.jpcc.8b06244@article{Schneider_2018,title={HowDoSecondaryPhosphineOxidesInteractwithSilverNanoclusters?InsightsfromComputation},volume={122},ISSN={1932-7455},url={http://dx.doi.org/10.1021/acs.jpcc.8b06244},DOI={10.1021/acs.jpcc.8b06244},number={37},journal={TheJournalofPhysicalChemistryC},publisher={AmericanChemicalSociety(ACS)},author={Schneider,FelipeS.S.andSegala,MaximilianoandCaramori,GiovanniF.anddaSilva,EderHenriqueandParreira,RenatoL.T.andSchrekker,HenriS.andvanLeeuwen,PietW.N.M.},year={2018},month={Aug},pages={21449–21461}}~doi2bib10.1021/acs.jpcc.8b06244~doi2text10.1021/acs.jpcc.8b06244Schneider,FelipeS.S.,MaximilianoSegala,GiovanniF.Caramori,EderHenriquedaSilva,RenatoL.T.Parreira,HenriS.Schrekker,ePietW.N.M.vanLeeuwen.2018.“HowDoSecondaryPhosphineOxidesInteractwithSilverNanoclusters?InsightsfromComputation”.TheJournalofPhysicalChemistryC122(37)(agosto27):21449–21461.doi:10.1021/acs.jpcc.8b06244.http://dx.doi.org/10.1021/acs.jpcc.8b06244.~doi2text-l10.1021/acs.jpcc.8b06244~doi2text-lde-DE10.1021/acs.jpcc.8b06244daSilva,RenatoL.T.Parreira,HenriS.Schrekker,undPietW.N.M.vanLeeuwen.2018.„HowDoSecondaryPhosphineOxidesInteractwithSilverNanoclusters?InsightsfromComputation“.TheJournalofPhysicalChemistryC122(37)(August27):21449–21461.doi:10.1021/acs.jpcc.8b06244.http://dx.doi.org/10.1021/acs.jpcc.8b06244.~doi2text-l10.1021/acs.jpcc.8b06244~doi2text-le10.1021/acs.jpcc.8b06244~doi2text-len10.1021/acs.jpcc.8b06244~doi2text-len-10.1021/acs.jpcc.8b06244~doi2text-len-G10.1021/acs.jpcc.8b06244~doi2text-len-GB10.1021/acs.jpcc.8b06244~doi2text-len-GB10.1021/acs.jpcc.8b06244#~doi2text-len-GB10.1021/acs.jpcc.8b06244#a~doi2text-len-GB10.1021/acs.jpcc.8b06244#al~doi2text-len-GB10.1021/acs.jpcc.8b06244#all~doi2text-len-GB10.1021/acs.jpcc.8b06244#allc~doi2text-len-GB10.1021/acs.jpcc.8b06244#allch~doi2text-len-GB10.1021/acs.jpcc.8b06244#allchi~doi2text-len-GB10.1021/acs.jpcc.8b06244#allchic~doi2text-len-GB10.1021/acs.jpcc.8b06244#allchica~doi2text-len-GB10.1021/acs.jpcc.8b06244#allchicag~doi2text-len-GB10.1021/acs.jpcc.8b06244#allchicago~doi2bib~doi2text-len-GB10.1021/acs.jpcc.8b06244#allchicagost~doi2text-len-GB10.1021/acs.jpcc.8b06244#allchicagosty~doi2text-len-GB10.1021/acs.jpcc.8b06244#allchicagostyl~doi2text-len-GB10.1021/acs.jpcc.8b06244#allchicagostyle~doi2text-len-GB10.1021/acs.jpcc.8b06244#allchicagostyleb~doi2text-len-GB10.1021/acs.jpcc.8b06244#allchicagostyleby~doi2text-len-GB10.1021/acs.jpcc.8b06244#allchicagostylebyd~doi2text-len-GB10.1021/acs.jpcc.8b06244#allchicagostylebyde~doi2text-len-GB10.1021/acs.jpcc.8b06244#allchicagostylebydef~doi2text-len-GB10.1021/acs.jpcc.8b06244#allchicagostylebydefa~doi2text-len-GB10.1021/acs.jpcc.8b06244#allchicagostylebydefau~doi2text-len-GB10.1021/acs.jpcc.8b06244#allchicagostylebydefaul~doi2text-len-GB10.1021/acs.jpcc.8b06244#allchicagostylebydefaultdaSilva,RenatoL.T.Parreira,HenriS.Schrekker,andPietW.N.M.vanLeeuwen.2018.‘HowDoSecondaryPhosphineOxidesInteractwithSilverNanoclusters?InsightsfromComputation’.TheJournalofPhysicalChemistryC122(37)(Augus~doi2text-s10.1021/acs.jpcc.8b06244~doi2text-sharvard310.1021/acs.jpcc.8b06244Schneider,F.S.S.etal.,2018.HowDoSecondaryPhosphineOxidesInteractwithSilverNanoclusters?InsightsfromComputation.TheJournalofPhysicalChemistryC,122(37),p.21449–21461.Availableat:http://dx.doi.org/10.1021/acs.jpcc.8b06244.~doi2text-samerican-chemical-society10.1021/acs.jpcc.8b06244(1)Schneider,F.S.S.;Segala,M.;Caramori,G.F.;daSilva,E.H.;Parreira,R.L.T.;Schrekker,H.S.;vanLeeuwen,P.W.N.M.TheJournalofPhysicalChemistryC2018,122,21449–21461.~doi2text-samerican-chemical-society10.1021/acs.jpcc.8b0624410.1103/PhysRev.140.A113310.1021/la203078wRev.140.A113310.1021/la203078w#Rev.140.A113310.1021/la203078w#asRev.140.A113310.1021/la203078w#asmanyRev.140.A113310.1021/la203078w#asmanyasRev.140.A113310.1021/la203078w#asmanyasyouRev.140.A113310.1021/la203078w#asmanyasyouwant(1)Kohn,W.;Sham,L.J.PhysicalReview1965,140,A1133–A1138.(1)Cabaleiro-Lago,C.;Szczepankiewicz,O.;Linse,S.Langmuir2012,28,1852–1857.~url2bib~url2bibhttps://github.com/schneiderfelipe/doi@misc{github-schneiderfelipe-doi-easily-convert-digital-object-identifier-doi-and-uniform-resource-locator-url-to-bibtex,title={GitHub-schneiderfelipe/doi:EasilyconvertDigitalObjectIdentifier(DOI)andUniformResourceLocator(URL)toBibTeX.},howpublished={\url{https://github.com/schneiderfelipe/doi}},note={Accessed:2020-09-22}~#~#have~#havefun~#havefun:)~~d~do~doi~doi2~doi2b~doi2bi~doi2bibhttps://doi.org/10.1021/acs.jpcc.8b06244~doi2bibhttps://doi.org10.1021/acs.jpcc.8b06244~doi2bibhttps://doi.or10.1021/acs.jpcc.8b06244~doi2bibhttps://doi.o10.1021/acs.jpcc.8b06244~doi2bibhttps://doi.10.1021/acs.jpcc.8b06244~doi2bibhttps://doi10.1021/acs.jpcc.8b06244~doi2bibhttps://do10.1021/acs.jpcc.8b06244~doi2bibhttps://d10.1021/acs.jpcc.8b06244~doi2bibhttps://10.1021/acs.jpcc.8b06244~doi2bibhttps:/10.1021/acs.jpcc.8b06244~doi2bibhttps:10.1021/acs.jpcc.8b06244~doi2bibhttps10.1021/acs.jpcc.8b06244~doi2bibhttp10.1021/acs.jpcc.8b06244~doi2bibhtt10.1021/acs.jpcc.8b06244~doi2bibht10.1021/acs.jpcc.8b06244~doi2bibh10.1021/acs.jpcc.8b06244~doi2bib10.1021/acs.jpcc.8b06244~doi2bi10.1021/acs.jpcc.8b06244~doi2b10.1021/acs.jpcc.8b06244~doi210.1021/acs.jpcc.8b06244~doi2t10.1021/acs.jpcc.8b06244~doi2te10.1021/acs.jpcc.8b06244~doi2tex10.1021/acs.jpcc.8b06244~doi2text10.1021/acs.jpcc.8b06244~doi2text-10.1021/acs.jpcc.8b06244~doi2text-l10.1021/acs.jpcc.8b06244~doi2text-ld10.1021/acs.jpcc.8b06244~doi2text-lde10.1021/acs.jpcc.8b06244~doi2text-lde-10.1021/acs.jpcc.8b06244~doi2text-lde-D10.1021/acs.jpcc.8b06244~doi2text-lde-DE10.1021/acs.jpcc.8b06244~doi2text-le-DE10.1021/acs.jpcc.8b06244~doi2text-l-DE10.1021/acs.jpcc.8b06244~doi2text-lDE10.1021/acs.jpcc.8b06244~doi2text-lE10.1021/acs.jpcc.8b06244~doi2text-10.1021/acs.jpcc.8b06244~doi2text-s10.1021/acs.jpcc.8b06244~doi2text-sh10.1021/acs.jpcc.8b06244~doi2text-sha10.1021/acs.jpcc.8b06244~doi2text-shar10.1021/acs.jpcc.8b06244~doi2text-sharv10.1021/acs.jpcc.8b06244~doi2text-sharva10.1021/acs.jpcc.8b06244~doi2text-sharvar10.1021/acs.jpcc.8b06244~doi2text-sharvard10.1021/acs.jpcc.8b06244~doi2text-sarvard310.1021/acs.jpcc.8b06244~doi2text-srvard310.1021/acs.jpcc.8b06244~doi2text-svard310.1021/acs.jpcc.8b06244~doi2text-sard310.1021/acs.jpcc.8b06244~doi2text-srd310.1021/acs.jpcc.8b06244~doi2text-sd310.1021/acs.jpcc.8b06244~doi2text-s310.1021/acs.jpcc.8b06244~doi2text-sa10.1021/acs.jpcc.8b06244~doi2text-sam10.1021/acs.jpcc.8b06244~doi2text-same10.1021/acs.jpcc.8b06244~doi2text-samer10.1021/acs.jpcc.8b06244~doi2text-sameri10.1021/acs.jpcc.8b06244~doi2text-sameric10.1021/acs.jpcc.8b06244~doi2text-samerica10.1021/acs.jpcc.8b06244~doi2text-samerican10.1021/acs.jpcc.8b06244~doi2text-samerican-10.1021/acs.jpcc.8b06244~doi2text-samerican-c10.1021/acs.jpcc.8b06244~doi2text-samerican-ch10.1021/acs.jpcc.8b06244~doi2text-samerican-che10.1021/acs.jpcc.8b06244~doi2text-samerican-chem10.1021/acs.jpcc.8b06244~doi2text-samerican-chemi10.1021/acs.jpcc.8b06244~doi2text-samerican-chemic10.1021/acs.jpcc.8b06244~doi2text-samerican-chemica10.1021/acs.jpcc.8b06244~doi2text-samerican-chemical10.1021/acs.jpcc.8b06244~doi2text-samerican-chemical-10.1021/acs.jpcc.8b06244~doi2text-samerican-chemical-s10.1021/acs.jpcc.8b06244~doi2text-samerican-chemical-so10.1021/acs.jpcc.8b06244~doi2text-samerican-chemical-soc10.1021/acs.jpcc.8b06244~doi2text-samerican-chemical-soci10.1021/acs.jpcc.8b06244~doi2text-samerican-chemical-socie10.1021/acs.jpcc.8b06244~doi2text-samerican-chemical-societ10.1021/acs.jpcc.8b06244~doi2text-samerican-chemical-society10.1021/acs.jpcc.8b0624410.1103/PhysRev.140.A1133Rev.140.A1133Rev.140.A113310.1021/la203078wRev.140.A113310.1021/la203078w#aRev.140.A113310.1021/la203078w#asmRev.140.A113310.1021/la203078w#asmaRev.140.A113310.1021/la203078w#asmanRev.140.A113310.1021/la203078w#asmanyaRev.140.A113310.1021/la203078w#asmanyasyRev.140.A113310.1021/la203078w#asmanyasyoRev.140.A113310.1021/la203078w#asmanyasyouwRev.140.A113310.1021/la203078w#asmanyasyouwaRev.140.A113310.1021/la203078w#asmanyasyouwan~u~ur~url~url2~url2b~url2bi~url2bibhttps://github.com/schneiderfelipe/doi~#h~#ha~#hav~#havef~#havefu~#havefun: diff --git a/src/doi2bib b/src/doi2bib index 9371b85..ab5ee4d 100755 --- a/src/doi2bib +++ b/src/doi2bib @@ -39,16 +39,16 @@ #? #? License: #? -#? doi2bib +#? doi2bib #? is licensed under the MIT License. while getopts ":hv" opt "$@"; do case $opt in - h) # help + h) # help grep "^#?" "$0" | cut -c 4- exit 0 ;; - v) # version + v) # version grep "^#? doi2bib" "$0" | cut -c 4- exit 0 ;; @@ -64,21 +64,21 @@ while getopts ":hv" opt "$@"; do done # recover $@ -shift $((OPTIND-1)) +shift $((OPTIND - 1)) # we consider everything after the command a list of DOIs. for doi in "$@"; do # we do some encoding as needed. doi=${doi//+/%2B} http_headers="Accept: text/bibliography; style=bibtex" - + echo "$http_headers" echo # we retrieve the data from https://doi.org/ - curl -LH "$http_headers" "https://doi.org/${doi}" | \ - sed -e $'s/^ *//' \ - -e $'s/}, /},\\\n /g' \ - -e $'s/, /,\\\n /1' \ - -e $'s/}}$/}\\\n}\\\n/' + curl -LH "$http_headers" "https://doi.org/${doi}" \ + | sed -e $'s/^ *//' \ + -e $'s/}, /},\\\n /g' \ + -e $'s/, /,\\\n /1' \ + -e $'s/}}$/}\\\n}\\\n/' done diff --git a/src/doi2text b/src/doi2text index 06a030e..019762d 100755 --- a/src/doi2text +++ b/src/doi2text @@ -52,7 +52,7 @@ #? #? License: #? -#? doi2text and doi2bib +#? doi2text and doi2bib #? are licensed under the MIT License. style="american-chemical-society" @@ -64,30 +64,30 @@ fi while getopts ":hvs:Sl:L" opt "$@"; do case $opt in - h) # help + h) # help grep "^#?" "$0" | cut -c 4- exit 0 ;; - v) # version + v) # version grep "^#? doi2text" "$0" | cut -c 4- exit 0 ;; - s) # style + s) # style style="${OPTARG}" ;; - S) # list all styles + S) # list all styles curl -X GET \ - https://api.github.com/repos/citation-style-language/styles/contents/\?ref\=master | \ - sed -e '/"name":.*\.csl"/{s/\s*"name": "//;s/\.csl",\s*//;p};d' + https://api.github.com/repos/citation-style-language/styles/contents/\?ref\=master \ + | sed -e '/"name":.*\.csl"/{s/\s*"name": "//;s/\.csl",\s*//;p};d' exit 0 ;; - l) # locale + l) # locale locale="${OPTARG}" ;; - L) # list all styles + L) # list all styles curl -X GET \ - https://api.github.com/repos/citation-style-language/locales/contents/\?ref\=master | \ - sed -e '/"name":.*\.xml"/{s/\s*"name": "//;s/\.xml",\s*//;s/locales-//;p};d' + https://api.github.com/repos/citation-style-language/locales/contents/\?ref\=master \ + | sed -e '/"name":.*\.xml"/{s/\s*"name": "//;s/\.xml",\s*//;s/locales-//;p};d' exit 0 ;; \?) @@ -102,14 +102,14 @@ while getopts ":hvs:Sl:L" opt "$@"; do done # recover $@ -shift $((OPTIND-1)) +shift $((OPTIND - 1)) # we consider everything after the command a list of DOIs. for doi in "$@"; do # we do some encoding as needed. doi=${doi//+/%2B} http_headers="Accept: text/x-bibliography; style=${style}; locale=${locale}" - + echo "$http_headers" echo diff --git a/src/url2bib b/src/url2bib index 820e5a6..440db8f 100755 --- a/src/url2bib +++ b/src/url2bib @@ -33,22 +33,22 @@ #? #? Examples: #? -#? $ url2bib https://github.com/schneiderfelipe/doi2bib # write to stdout -#? $ url2bib https://github.com/schneiderfelipe/doi2bib >> citations.bib # append to file -#? $ url2bib https://github.com/schneiderfelipe/doi2bib https://en.wikipedia.org/wiki/Brazilin # get many URLs +#? $ url2bib https://github.com/schneiderfelipe/doi # write to stdout +#? $ url2bib https://github.com/schneiderfelipe/doi >> citations.bib # append to file +#? $ url2bib https://github.com/schneiderfelipe/doi https://en.wikipedia.org/wiki/Brazilin # get many URLs #? #? License: #? -#? url2bib and doi2bib +#? url2bib and doi2bib #? are licensed under the MIT License. while getopts ":hv" opt "$@"; do case $opt in - h) # help + h) # help grep "^#?" "$0" | cut -c 4- exit 0 ;; - v) # version + v) # version grep "^#? url2bib" "$0" | cut -c 4- exit 0 ;; @@ -64,7 +64,7 @@ while getopts ":hv" opt "$@"; do done # recover $@ -shift $((OPTIND-1)) +shift $((OPTIND - 1)) # we consider everything after the command a list of URLs. for url in "$@"; do @@ -72,14 +72,14 @@ for url in "$@"; do url=${url//+/%2B} # https://unix.stackexchange.com/a/103253/211802 - title=$(wget -qO- "${url}" | \ - gawk -v IGNORECASE=1 -v RS=']*>/,"");print;exit}') + title=$(wget -qO- "${url}" \ + | gawk -v IGNORECASE=1 -v RS=']*>/,"");print;exit}') # https://gist.github.com/oneohthree/f528c7ae1e701ad990e6 - slug=$(echo "${title}" | iconv -t ascii//TRANSLIT | \ - sed -r s/[^a-zA-Z0-9]+/-/g | sed -r s/^-+\|-+$//g | tr "[:upper:]" "[:lower:]") + slug=$(echo "${title}" | iconv -t ascii//TRANSLIT \ + | sed -r s/[^a-zA-Z0-9]+/-/g | sed -r s/^-+\|-+$//g | tr "[:upper:]" "[:lower:]") - cat < Date: Fri, 28 Apr 2023 14:44:51 -0300 Subject: [PATCH 17/29] feat: create a library --- src/bin/doi2bib.rs | 108 +-------------------------------------------- src/lib.rs | 108 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 106 deletions(-) create mode 100644 src/lib.rs diff --git a/src/bin/doi2bib.rs b/src/bin/doi2bib.rs index 1e569d5..53db5e4 100644 --- a/src/bin/doi2bib.rs +++ b/src/bin/doi2bib.rs @@ -1,15 +1,12 @@ -use std::fmt; use std::process; use std::str::FromStr; use clap::Parser; -use itertools::Itertools; -use nutype::nutype; +use doi::Doi; use reqwest::blocking::Client; use reqwest::blocking::Response; use reqwest::header; -use thiserror::Error; -use url::Url; +use reqwest::Url; /// Retrieve BibTeX data for a given DOI. #[derive(Clone, Debug, Parser)] @@ -20,107 +17,6 @@ struct Cli { doi: Vec, } -#[derive(Clone, Debug)] -struct Doi { - prefix: DoiPrefix, - suffix: DoiSuffix, -} - -#[derive(Clone, Debug, Error)] -enum DoiParseError { - #[error("slash not found")] - SlashNotFound, - - #[error("not in DOI namespace")] - NotInNamespace, - - #[error("failed to parse prefix subdivision: {0}")] - PrefixSubdivision(#[from] DoiPrefixSubdivisionError), - - #[error("failed to parse suffix: {0}")] - Suffix(#[from] DoiSuffixError), -} - -impl FromStr for Doi { - type Err = DoiParseError; - - fn from_str(s: &str) -> Result { - let s = s.strip_prefix(DOI_ORG).unwrap_or(s); - let s = s.strip_prefix("doi:").unwrap_or(s); - - let (prefix, suffix) = s.split_once('/').ok_or(DoiParseError::SlashNotFound)?; - Ok(Self { - prefix: prefix.parse()?, - suffix: suffix.parse()?, - }) - } -} - -#[derive(Clone, Debug)] -struct DoiPrefix { - subdivisions: Vec, -} - -#[nutype( - sanitize(trim, lowercase) - validate(not_empty) -)] -#[derive(AsRef, Clone, Debug, Display, FromStr)] -struct DoiPrefixSubdivision(String); - -impl FromStr for DoiPrefix { - type Err = DoiParseError; - - fn from_str(s: &str) -> Result { - let s = s - .strip_prefix(DOI_NAMESPACE) - .ok_or(DoiParseError::NotInNamespace)?; - let subdivisions: Result<_, _> = s.split('.').map(str::parse).collect(); - let subdivisions = subdivisions?; - Ok(Self { subdivisions }) - } -} - -#[nutype( - sanitize(trim, lowercase) - validate(not_empty) -)] -#[derive(AsRef, Clone, Debug, Display, FromStr)] -struct DoiSuffix(String); - -impl fmt::Display for Doi { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}/{}", self.prefix, self.suffix) - } -} - -impl fmt::Display for DoiPrefix { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{DOI_NAMESPACE}")?; - self.subdivisions - .iter() - .map(AsRef::as_ref) - .intersperse(".") - .try_for_each(|s| write!(f, "{s}")) - } -} - -const DOI_NAMESPACE: &str = "10."; -const DOI_ORG: &str = "https://doi.org/"; - -impl From for Url { - fn from(doi: Doi) -> Self { - Self::parse(&format!("{DOI_ORG}{doi}")).expect("DOI should always be translatable to URL") - } -} - -impl From<&Doi> for Url { - fn from(doi: &Doi) -> Self { - Self::parse(&format!("https://doi.org/{doi}")) - .expect("DOI should always be translatable to URL") - } -} - fn main() { let cli = Cli::parse(); diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..fb3433b --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,108 @@ +use std::fmt; +use std::str::FromStr; + +use itertools::Itertools; +use nutype::nutype; +use thiserror::Error; +use url::Url; + +#[derive(Clone, Debug)] +pub struct Doi { + prefix: DoiPrefix, + suffix: DoiSuffix, +} + +#[derive(Clone, Debug, Error)] +pub enum DoiParseError { + #[error("slash not found")] + SlashNotFound, + + #[error("not in DOI namespace")] + NotInNamespace, + + #[error("failed to parse prefix subdivision: {0}")] + PrefixSubdivision(#[from] DoiPrefixSubdivisionError), + + #[error("failed to parse suffix: {0}")] + Suffix(#[from] DoiSuffixError), +} + +impl FromStr for Doi { + type Err = DoiParseError; + + fn from_str(s: &str) -> Result { + let s = s.strip_prefix(DOI_ORG).unwrap_or(s); + let s = s.strip_prefix("doi:").unwrap_or(s); + + let (prefix, suffix) = s.split_once('/').ok_or(DoiParseError::SlashNotFound)?; + Ok(Self { + prefix: prefix.parse()?, + suffix: suffix.parse()?, + }) + } +} + +#[derive(Clone, Debug)] +struct DoiPrefix { + subdivisions: Vec, +} + +#[nutype( + sanitize(trim, lowercase) + validate(not_empty) +)] +#[derive(AsRef, Clone, Debug, Display, FromStr)] +struct DoiPrefixSubdivision(String); + +impl FromStr for DoiPrefix { + type Err = DoiParseError; + + fn from_str(s: &str) -> Result { + let s = s + .strip_prefix(DOI_NAMESPACE) + .ok_or(DoiParseError::NotInNamespace)?; + let subdivisions: Result<_, _> = s.split('.').map(str::parse).collect(); + let subdivisions = subdivisions?; + Ok(Self { subdivisions }) + } +} + +#[nutype( + sanitize(trim, lowercase) + validate(not_empty) +)] +#[derive(AsRef, Clone, Debug, Display, FromStr)] +struct DoiSuffix(String); + +impl fmt::Display for Doi { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}/{}", self.prefix, self.suffix) + } +} + +impl fmt::Display for DoiPrefix { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{DOI_NAMESPACE}")?; + self.subdivisions + .iter() + .map(AsRef::as_ref) + .intersperse(".") + .try_for_each(|s| write!(f, "{s}")) + } +} + +const DOI_NAMESPACE: &str = "10."; +const DOI_ORG: &str = "https://doi.org/"; + +impl From for Url { + fn from(doi: Doi) -> Self { + Self::parse(&format!("{DOI_ORG}{doi}")).expect("DOI should always be translatable to URL") + } +} + +impl From<&Doi> for Url { + fn from(doi: &Doi) -> Self { + Self::parse(&format!("https://doi.org/{doi}")) + .expect("DOI should always be translatable to URL") + } +} From 6cbc18f701f1704790d694ec5cb0da4d29d26106 Mon Sep 17 00:00:00 2001 From: "Felipe S. S. Schneider" Date: Fri, 28 Apr 2023 16:22:45 -0300 Subject: [PATCH 18/29] refactor: lib --- Cargo.toml | 2 + src/lib.rs | 113 +++++++++++++---------------------------------------- 2 files changed, 30 insertions(+), 85 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 162b729..2fb7d51 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,8 @@ edition = "2021" clap = { version = "4.2.5", features = ["derive"] } itertools = "0.10.5" nutype = "0.2.0" +once_cell = "1.17.1" +ouroboros = "0.15.6" reqwest = { version = "0.11", features = ["blocking"] } thiserror = "1.0.40" url = "2.3.1" diff --git a/src/lib.rs b/src/lib.rs index fb3433b..d71a52f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,108 +1,51 @@ -use std::fmt; -use std::str::FromStr; +//! A strongly-typed library for working +//! with +//! [Digital object identifiers](https://en.wikipedia.org/wiki/Digital_object_identifier). +//! (DOIs). -use itertools::Itertools; use nutype::nutype; -use thiserror::Error; +use once_cell::sync::Lazy; use url::Url; -#[derive(Clone, Debug)] -pub struct Doi { - prefix: DoiPrefix, - suffix: DoiSuffix, -} - -#[derive(Clone, Debug, Error)] -pub enum DoiParseError { - #[error("slash not found")] - SlashNotFound, - - #[error("not in DOI namespace")] - NotInNamespace, - - #[error("failed to parse prefix subdivision: {0}")] - PrefixSubdivision(#[from] DoiPrefixSubdivisionError), - - #[error("failed to parse suffix: {0}")] - Suffix(#[from] DoiSuffixError), -} - -impl FromStr for Doi { - type Err = DoiParseError; - - fn from_str(s: &str) -> Result { - let s = s.strip_prefix(DOI_ORG).unwrap_or(s); - let s = s.strip_prefix("doi:").unwrap_or(s); - - let (prefix, suffix) = s.split_once('/').ok_or(DoiParseError::SlashNotFound)?; - Ok(Self { - prefix: prefix.parse()?, - suffix: suffix.parse()?, - }) - } -} +/// Prefix URL for a +/// [Digital object identifier](https://en.wikipedia.org/wiki/Digital_object_identifier). +static URL: Lazy = + Lazy::new(|| Url::parse("https://doi.org/").expect("URL should be parseable")); -#[derive(Clone, Debug)] -struct DoiPrefix { - subdivisions: Vec, -} +/// Namespace of a +/// [Digital object identifier](https://en.wikipedia.org/wiki/Digital_object_identifier). +const NAMESPACE: &str = "10."; +/// A +/// [Digital object identifier](https://en.wikipedia.org/wiki/Digital_object_identifier). #[nutype( - sanitize(trim, lowercase) - validate(not_empty) + sanitize(trim, lowercase, with = sanitize_doi) + validate(not_empty, with = validate_doi) )] -#[derive(AsRef, Clone, Debug, Display, FromStr)] -struct DoiPrefixSubdivision(String); - -impl FromStr for DoiPrefix { - type Err = DoiParseError; +#[derive(*)] +#[derive(Display)] +pub struct Doi(String); - fn from_str(s: &str) -> Result { - let s = s - .strip_prefix(DOI_NAMESPACE) - .ok_or(DoiParseError::NotInNamespace)?; - let subdivisions: Result<_, _> = s.split('.').map(str::parse).collect(); - let subdivisions = subdivisions?; - Ok(Self { subdivisions }) - } +#[allow(clippy::map_unwrap_or)] +fn sanitize_doi(doi: String) -> String { + [URL.as_str(), "doi:"].into_iter().fold(doi, |doi, prefix| { + doi.strip_prefix(prefix).map(Into::into).unwrap_or(doi) + }) } -#[nutype( - sanitize(trim, lowercase) - validate(not_empty) -)] -#[derive(AsRef, Clone, Debug, Display, FromStr)] -struct DoiSuffix(String); - -impl fmt::Display for Doi { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}/{}", self.prefix, self.suffix) - } +fn validate_doi(doi: &str) -> bool { + doi.starts_with(NAMESPACE) && doi.contains('/') } -impl fmt::Display for DoiPrefix { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{DOI_NAMESPACE}")?; - self.subdivisions - .iter() - .map(AsRef::as_ref) - .intersperse(".") - .try_for_each(|s| write!(f, "{s}")) - } -} - -const DOI_NAMESPACE: &str = "10."; -const DOI_ORG: &str = "https://doi.org/"; - impl From for Url { fn from(doi: Doi) -> Self { - Self::parse(&format!("{DOI_ORG}{doi}")).expect("DOI should always be translatable to URL") + Self::from(&doi) } } impl From<&Doi> for Url { fn from(doi: &Doi) -> Self { - Self::parse(&format!("https://doi.org/{doi}")) + URL.join(doi.as_ref()) .expect("DOI should always be translatable to URL") } } From 5e2f731d54ed69986c5c52f905cd423edb2da584 Mon Sep 17 00:00:00 2001 From: "Felipe S. S. Schneider" Date: Fri, 28 Apr 2023 16:31:00 -0300 Subject: [PATCH 19/29] docs: improve --- src/lib.rs | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d71a52f..a6e0676 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,23 +1,26 @@ //! A strongly-typed library for working //! with -//! [Digital object identifiers](https://en.wikipedia.org/wiki/Digital_object_identifier). +//! [Digital object identifiers](https://en.wikipedia.org/wiki/Digital_object_identifier) //! (DOIs). use nutype::nutype; use once_cell::sync::Lazy; use url::Url; -/// Prefix URL for a -/// [Digital object identifier](https://en.wikipedia.org/wiki/Digital_object_identifier). +/// Prefix URL for retrieving information about +/// [Digital object identifiers](https://en.wikipedia.org/wiki/Digital_object_identifier) +/// (DOIs). static URL: Lazy = Lazy::new(|| Url::parse("https://doi.org/").expect("URL should be parseable")); -/// Namespace of a -/// [Digital object identifier](https://en.wikipedia.org/wiki/Digital_object_identifier). +/// Namespace of +/// [Digital object identifiers](https://en.wikipedia.org/wiki/Digital_object_identifier) +/// (DOIs). const NAMESPACE: &str = "10."; /// A -/// [Digital object identifier](https://en.wikipedia.org/wiki/Digital_object_identifier). +/// [Digital object identifier](https://en.wikipedia.org/wiki/Digital_object_identifier) +/// (DOI). #[nutype( sanitize(trim, lowercase, with = sanitize_doi) validate(not_empty, with = validate_doi) @@ -26,13 +29,22 @@ const NAMESPACE: &str = "10."; #[derive(Display)] pub struct Doi(String); +/// Sanitize a +/// [Digital object identifier](https://en.wikipedia.org/wiki/Digital_object_identifier) +/// (DOI). #[allow(clippy::map_unwrap_or)] fn sanitize_doi(doi: String) -> String { - [URL.as_str(), "doi:"].into_iter().fold(doi, |doi, prefix| { - doi.strip_prefix(prefix).map(Into::into).unwrap_or(doi) - }) + [URL.as_str(), "doi:"] + .into_iter() + .fold(doi.as_str(), |doi, prefix| { + doi.strip_prefix(prefix).unwrap_or(doi) + }) + .into() } +/// Validate a +/// [Digital object identifier](https://en.wikipedia.org/wiki/Digital_object_identifier) +/// (DOI). fn validate_doi(doi: &str) -> bool { doi.starts_with(NAMESPACE) && doi.contains('/') } From 8319555339c515a50c51a7629ca3e9237903bee4 Mon Sep 17 00:00:00 2001 From: "Felipe S. S. Schneider" Date: Fri, 28 Apr 2023 16:41:41 -0300 Subject: [PATCH 20/29] feat: validate with Regex --- Cargo.toml | 3 ++- src/lib.rs | 31 +++++++++++++++---------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2fb7d51..8878b0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,9 +8,10 @@ edition = "2021" [dependencies] clap = { version = "4.2.5", features = ["derive"] } itertools = "0.10.5" -nutype = "0.2.0" +nutype = { version = "0.2.0", features = ["regex"] } once_cell = "1.17.1" ouroboros = "0.15.6" +regex = "1.8.1" reqwest = { version = "0.11", features = ["blocking"] } thiserror = "1.0.40" url = "2.3.1" diff --git a/src/lib.rs b/src/lib.rs index a6e0676..2f97b32 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,25 +5,32 @@ use nutype::nutype; use once_cell::sync::Lazy; +use regex::Regex; use url::Url; /// Prefix URL for retrieving information about /// [Digital object identifiers](https://en.wikipedia.org/wiki/Digital_object_identifier) /// (DOIs). -static URL: Lazy = - Lazy::new(|| Url::parse("https://doi.org/").expect("URL should be parseable")); +static URL: Lazy = Lazy::new(|| Url::parse("https://doi.org/").expect("should be parseable")); -/// Namespace of -/// [Digital object identifiers](https://en.wikipedia.org/wiki/Digital_object_identifier) -/// (DOIs). -const NAMESPACE: &str = "10."; +/// Regex for +/// [Digital object identifier](https://en.wikipedia.org/wiki/Digital_object_identifier) +/// (DOI) +/// validation. +/// +/// It is taken from +/// , +/// which has been +/// [recommended by CrossRef](https://www.crossref.org/blog/dois-and-matching-regular-expressions/). +static REGEX: Lazy = + Lazy::new(|| Regex::new(r"/^10.\d{4,9}/[-._;()/:A-Z0-9]+$/i").expect("should be valid")); /// A /// [Digital object identifier](https://en.wikipedia.org/wiki/Digital_object_identifier) /// (DOI). #[nutype( sanitize(trim, lowercase, with = sanitize_doi) - validate(not_empty, with = validate_doi) + validate(not_empty, regex = REGEX) )] #[derive(*)] #[derive(Display)] @@ -42,13 +49,6 @@ fn sanitize_doi(doi: String) -> String { .into() } -/// Validate a -/// [Digital object identifier](https://en.wikipedia.org/wiki/Digital_object_identifier) -/// (DOI). -fn validate_doi(doi: &str) -> bool { - doi.starts_with(NAMESPACE) && doi.contains('/') -} - impl From for Url { fn from(doi: Doi) -> Self { Self::from(&doi) @@ -57,7 +57,6 @@ impl From for Url { impl From<&Doi> for Url { fn from(doi: &Doi) -> Self { - URL.join(doi.as_ref()) - .expect("DOI should always be translatable to URL") + URL.join(doi.as_ref()).expect("should be joinable") } } From 1461dbea842ec32034be1440bdebd60e1057f969 Mon Sep 17 00:00:00 2001 From: "Felipe S. S. Schneider" Date: Fri, 28 Apr 2023 16:45:27 -0300 Subject: [PATCH 21/29] refactor: reorganize --- src/lib.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2f97b32..f85f7af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,12 +6,6 @@ use nutype::nutype; use once_cell::sync::Lazy; use regex::Regex; -use url::Url; - -/// Prefix URL for retrieving information about -/// [Digital object identifiers](https://en.wikipedia.org/wiki/Digital_object_identifier) -/// (DOIs). -static URL: Lazy = Lazy::new(|| Url::parse("https://doi.org/").expect("should be parseable")); /// Regex for /// [Digital object identifier](https://en.wikipedia.org/wiki/Digital_object_identifier) @@ -36,10 +30,18 @@ static REGEX: Lazy = #[derive(Display)] pub struct Doi(String); +use url::Url; + +/// Prefix URL for retrieving information about +/// [Digital object identifiers](https://en.wikipedia.org/wiki/Digital_object_identifier) +/// (DOIs). +static URL: Lazy = Lazy::new(|| Url::parse("https://doi.org/").expect("should be parseable")); + /// Sanitize a /// [Digital object identifier](https://en.wikipedia.org/wiki/Digital_object_identifier) /// (DOI). -#[allow(clippy::map_unwrap_or)] +#[inline] +#[allow(clippy::needless_pass_by_value)] fn sanitize_doi(doi: String) -> String { [URL.as_str(), "doi:"] .into_iter() @@ -50,12 +52,14 @@ fn sanitize_doi(doi: String) -> String { } impl From for Url { + #[inline] fn from(doi: Doi) -> Self { Self::from(&doi) } } impl From<&Doi> for Url { + #[inline] fn from(doi: &Doi) -> Self { URL.join(doi.as_ref()).expect("should be joinable") } From 416f52073cc800e5f811983aa3f8863ad2f26ff9 Mon Sep 17 00:00:00 2001 From: "Felipe S. S. Schneider" Date: Fri, 28 Apr 2023 16:51:01 -0300 Subject: [PATCH 22/29] fix: regex --- src/lib.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f85f7af..5946e11 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ use nutype::nutype; use once_cell::sync::Lazy; use regex::Regex; +use regex::RegexBuilder; /// Regex for /// [Digital object identifier](https://en.wikipedia.org/wiki/Digital_object_identifier) @@ -16,8 +17,12 @@ use regex::Regex; /// , /// which has been /// [recommended by CrossRef](https://www.crossref.org/blog/dois-and-matching-regular-expressions/). -static REGEX: Lazy = - Lazy::new(|| Regex::new(r"/^10.\d{4,9}/[-._;()/:A-Z0-9]+$/i").expect("should be valid")); +static REGEX: Lazy = Lazy::new(|| { + RegexBuilder::new(r"^10.\d{4,9}/[-._;()/:A-Z0-9]+$") + .case_insensitive(true) + .build() + .expect("should be valid") +}); /// A /// [Digital object identifier](https://en.wikipedia.org/wiki/Digital_object_identifier) From ca2dc8f7be7dcb214d27c3b56b9d74732a6571fc Mon Sep 17 00:00:00 2001 From: "Felipe S. S. Schneider" Date: Fri, 28 Apr 2023 16:53:22 -0300 Subject: [PATCH 23/29] chore(deps): clean up --- Cargo.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8878b0e..f45d0a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,11 +7,8 @@ edition = "2021" [dependencies] clap = { version = "4.2.5", features = ["derive"] } -itertools = "0.10.5" nutype = { version = "0.2.0", features = ["regex"] } once_cell = "1.17.1" -ouroboros = "0.15.6" regex = "1.8.1" reqwest = { version = "0.11", features = ["blocking"] } -thiserror = "1.0.40" url = "2.3.1" From f756add888167e296ba83df505e4428903a6e83c Mon Sep 17 00:00:00 2001 From: "Felipe S. S. Schneider" Date: Fri, 28 Apr 2023 16:59:33 -0300 Subject: [PATCH 24/29] chore(Cargo.toml): set MSRV --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index f45d0a8..47eed0f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ name = "doi" version = "0.1.0" edition = "2021" +rust-version = "1.65.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 8004e6b81baae0b95b85d3365f64c364651bb05d Mon Sep 17 00:00:00 2001 From: "Felipe S. S. Schneider" Date: Fri, 28 Apr 2023 17:07:20 -0300 Subject: [PATCH 25/29] docs: add link --- src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 5946e11..e04dc58 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,7 +37,9 @@ pub struct Doi(String); use url::Url; -/// Prefix URL for retrieving information about +/// Prefix +/// [URL](https://en.wikipedia.org/wiki/URL) +/// for retrieving information about /// [Digital object identifiers](https://en.wikipedia.org/wiki/Digital_object_identifier) /// (DOIs). static URL: Lazy = Lazy::new(|| Url::parse("https://doi.org/").expect("should be parseable")); From 6f29c11e4af826f63e77d2f45d4532726e87ace4 Mon Sep 17 00:00:00 2001 From: "Felipe S. S. Schneider" Date: Fri, 28 Apr 2023 17:21:33 -0300 Subject: [PATCH 26/29] feat: improve writing --- Cargo.toml | 1 + src/bin/doi2bib.rs | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 47eed0f..454cbca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ rust-version = "1.65.0" [dependencies] clap = { version = "4.2.5", features = ["derive"] } +nom-bibtex = "0.4.0" nutype = { version = "0.2.0", features = ["regex"] } once_cell = "1.17.1" regex = "1.8.1" diff --git a/src/bin/doi2bib.rs b/src/bin/doi2bib.rs index 53db5e4..6d92ea8 100644 --- a/src/bin/doi2bib.rs +++ b/src/bin/doi2bib.rs @@ -3,6 +3,8 @@ use std::str::FromStr; use clap::Parser; use doi::Doi; +use nom_bibtex::Bibliography; +use nom_bibtex::Bibtex; use reqwest::blocking::Client; use reqwest::blocking::Response; use reqwest::header; @@ -28,7 +30,10 @@ fn main() { .and_then(Response::text) { Ok(bibtex_data) => { - println!("{bibtex_data}"); + let bibtex = Bibtex::parse(&bibtex_data).expect("should be valid"); + for biblio in bibtex.bibliographies() { + print(biblio); + } } Err(e) => { eprintln!("Error retrieving DOI {doi}: {e}"); @@ -37,3 +42,11 @@ fn main() { } } } + +fn print(biblio: &Bibliography) { + println!("@{}{{{},", biblio.entry_type(), biblio.citation_key()); + for (key, value) in biblio.tags() { + println!(" {key}={{{value}}},"); + } + println!("}}\n"); +} From 8c23d3669f6511bb92c26d28d2690f2e37a4b10d Mon Sep 17 00:00:00 2001 From: "Felipe S. S. Schneider" Date: Fri, 28 Apr 2023 17:31:57 -0300 Subject: [PATCH 27/29] feat: align --- Cargo.toml | 1 + src/bin/doi2bib.rs | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 454cbca..6a2d985 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ rust-version = "1.65.0" [dependencies] clap = { version = "4.2.5", features = ["derive"] } +itertools = "0.10.5" nom-bibtex = "0.4.0" nutype = { version = "0.2.0", features = ["regex"] } once_cell = "1.17.1" diff --git a/src/bin/doi2bib.rs b/src/bin/doi2bib.rs index 6d92ea8..3d8d42a 100644 --- a/src/bin/doi2bib.rs +++ b/src/bin/doi2bib.rs @@ -3,6 +3,8 @@ use std::str::FromStr; use clap::Parser; use doi::Doi; +use itertools::Itertools; +use itertools::Position; use nom_bibtex::Bibliography; use nom_bibtex::Bibtex; use reqwest::blocking::Client; @@ -45,8 +47,15 @@ fn main() { fn print(biblio: &Bibliography) { println!("@{}{{{},", biblio.entry_type(), biblio.citation_key()); - for (key, value) in biblio.tags() { - println!(" {key}={{{value}}},"); + for item in biblio.tags().iter().with_position() { + match item { + Position::First((key, value)) | Position::Middle((key, value)) => { + println!(" {key:9} = {{{value}}},"); + } + Position::Last((key, value)) | Position::Only((key, value)) => { + println!(" {key:9} = {{{value}}}"); + } + } } println!("}}\n"); } From 340a37c341d6ea9848e8191022436c6819fd2dda Mon Sep 17 00:00:00 2001 From: "Felipe S. S. Schneider" Date: Fri, 28 Apr 2023 17:40:27 -0300 Subject: [PATCH 28/29] refactor: migrate error --- Cargo.toml | 1 + src/bin/doi2bib.rs | 14 ++++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6a2d985..ec6841e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ rust-version = "1.65.0" [dependencies] clap = { version = "4.2.5", features = ["derive"] } +either = "1.8.1" itertools = "0.10.5" nom-bibtex = "0.4.0" nutype = { version = "0.2.0", features = ["regex"] } diff --git a/src/bin/doi2bib.rs b/src/bin/doi2bib.rs index 3d8d42a..45e57a3 100644 --- a/src/bin/doi2bib.rs +++ b/src/bin/doi2bib.rs @@ -3,6 +3,7 @@ use std::str::FromStr; use clap::Parser; use doi::Doi; +use either::Either; use itertools::Itertools; use itertools::Position; use nom_bibtex::Bibliography; @@ -30,12 +31,13 @@ fn main() { .header(header::ACCEPT, "text/bibliography; style=bibtex") .send() .and_then(Response::text) + .map_err(Either::Left) + .and_then(|text| Bibtex::parse(&text).map_err(Either::Right)) { - Ok(bibtex_data) => { - let bibtex = Bibtex::parse(&bibtex_data).expect("should be valid"); - for biblio in bibtex.bibliographies() { + Ok(bibtex) => { + bibtex.bibliographies().iter().for_each(|biblio| { print(biblio); - } + }); } Err(e) => { eprintln!("Error retrieving DOI {doi}: {e}"); @@ -47,7 +49,7 @@ fn main() { fn print(biblio: &Bibliography) { println!("@{}{{{},", biblio.entry_type(), biblio.citation_key()); - for item in biblio.tags().iter().with_position() { + biblio.tags().iter().with_position().for_each(|item| { match item { Position::First((key, value)) | Position::Middle((key, value)) => { println!(" {key:9} = {{{value}}},"); @@ -56,6 +58,6 @@ fn print(biblio: &Bibliography) { println!(" {key:9} = {{{value}}}"); } } - } + }); println!("}}\n"); } From 3f4d704265db3be9ff136431fe530626e63d3dde Mon Sep 17 00:00:00 2001 From: "Felipe S. S. Schneider" Date: Fri, 28 Apr 2023 17:50:22 -0300 Subject: [PATCH 29/29] feat: start implementing url2bib --- src/bin/url2bib.rs | 61 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/src/bin/url2bib.rs b/src/bin/url2bib.rs index e7a11a9..ca6320d 100644 --- a/src/bin/url2bib.rs +++ b/src/bin/url2bib.rs @@ -1,3 +1,62 @@ +use std::process; +use std::str::FromStr; + +use clap::Parser; +use itertools::Itertools; +use itertools::Position; +use nom_bibtex::Bibliography; +use reqwest::blocking::Client; +use reqwest::blocking::Response; +use reqwest::Url; + +/// Retrieve BibTeX data for a given URL. +#[derive(Clone, Debug, Parser)] +#[command(author, version, about, arg_required_else_help(true))] +struct Cli { + /// URL to retrieve BibTeX data for. + #[arg(value_parser(Url::from_str))] + url: Vec, +} + fn main() { - println!("Hello, world!"); + let cli = Cli::parse(); + + for url in cli.url { + match Client::new() + .get(url.as_str()) + .send() + .and_then(Response::text) + { + Ok(html) => { + let title: String = todo!("Hello world!"); + let slug: String = todo!("hello-world"); + let today: String = todo!("%Y-%m-%d"); + let biblio = Bibliography::new("misc".into(), slug, vec![ + ("title".into(), title), + ("howpublished".into(), format!(r"\url{{{}}}", url)), + ("note".into(), format!("Accessed: {}", today)), + ]); + print(&biblio); + } + Err(e) => { + eprintln!("Error retrieving URL {url}: {e}"); + process::exit(1); + } + } + } +} + +fn print(biblio: &Bibliography) { + println!("@{}{{{},", biblio.entry_type(), biblio.citation_key()); + biblio.tags().iter().with_position().for_each(|item| { + match item { + Position::First((key, value)) | Position::Middle((key, value)) => { + println!(" {key:9} = {{{value}}},"); + } + Position::Last((key, value)) | Position::Only((key, value)) => { + println!(" {key:9} = {{{value}}}"); + } + } + }); + println!("}}\n"); }