Compare commits
2
Commits
master
..
20eef0c0b0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20eef0c0b0
|
||
|
|
af063645c7
|
Generated
+3
-66
@@ -360,27 +360,6 @@ dependencies = [
|
|||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "dirs-next"
|
|
||||||
version = "2.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"dirs-sys-next",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "dirs-sys-next"
|
|
||||||
version = "0.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"redox_users",
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "displaydoc"
|
name = "displaydoc"
|
||||||
version = "0.2.5"
|
version = "0.2.5"
|
||||||
@@ -1010,16 +989,6 @@ version = "0.2.174"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
|
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "libredox"
|
|
||||||
version = "0.1.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 2.9.1",
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
@@ -1276,7 +1245,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323"
|
checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
"thiserror 2.0.12",
|
"thiserror",
|
||||||
"ucd-trie",
|
"ucd-trie",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1458,17 +1427,6 @@ dependencies = [
|
|||||||
"bitflags 2.9.1",
|
"bitflags 2.9.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "redox_users"
|
|
||||||
version = "0.4.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
|
|
||||||
dependencies = [
|
|
||||||
"getrandom 0.2.16",
|
|
||||||
"libredox",
|
|
||||||
"thiserror 1.0.69",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.11.1"
|
version = "1.11.1"
|
||||||
@@ -1919,33 +1877,13 @@ dependencies = [
|
|||||||
"unicode-width",
|
"unicode-width",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "thiserror"
|
|
||||||
version = "1.0.69"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
|
||||||
dependencies = [
|
|
||||||
"thiserror-impl 1.0.69",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "2.0.12"
|
version = "2.0.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
|
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl 2.0.12",
|
"thiserror-impl",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "thiserror-impl"
|
|
||||||
version = "1.0.69"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.104",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2386,10 +2324,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wikipedia-infobox-analyzer"
|
name = "wikipedia-infobox-analyzer"
|
||||||
version = "0.2.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ascii_table",
|
"ascii_table",
|
||||||
"dirs-next",
|
|
||||||
"itertools",
|
"itertools",
|
||||||
"mediawiki",
|
"mediawiki",
|
||||||
"regex",
|
"regex",
|
||||||
|
|||||||
+2
-3
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "wikipedia-infobox-analyzer"
|
name = "wikipedia-infobox-analyzer"
|
||||||
version = "0.2.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = """
|
description = """
|
||||||
This analysis tool allows seeing the wikidata behind articles through the lens of the infoboxes.
|
This analysis tool allows seeing the wikidata behind articles through the lens of the infoboxes.
|
||||||
@@ -8,7 +8,7 @@ This analysis tool allows seeing the wikidata behind articles through the lens o
|
|||||||
|
|
||||||
authors = ["Raymon Zutekouw <contact@raymon.dev>"]
|
authors = ["Raymon Zutekouw <contact@raymon.dev>"]
|
||||||
|
|
||||||
repository = "https://codeberg.org/MeltingSilicon/wikipedia-infobox-analyzer"
|
repository = ""
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
|
|
||||||
keywords = ["wikipedia", "wikidata", "infobox"]
|
keywords = ["wikipedia", "wikidata", "infobox"]
|
||||||
@@ -17,7 +17,6 @@ categories = ["command-line-utilities"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
regex = "1.11.1"
|
regex = "1.11.1"
|
||||||
ascii_table = "4.0.7"
|
ascii_table = "4.0.7"
|
||||||
dirs-next = "2.0.0"
|
|
||||||
itertools = "0.14.0"
|
itertools = "0.14.0"
|
||||||
mediawiki = "0.3.1"
|
mediawiki = "0.3.1"
|
||||||
reqwest = { version = "0.12", features = ["blocking"] }
|
reqwest = { version = "0.12", features = ["blocking"] }
|
||||||
|
|||||||
@@ -31,55 +31,6 @@ By default, the tool assumes that you are looking for tools on the English wikip
|
|||||||
Make sure the passed title matches the article title, and the tool should be able to find the wikidata entry.
|
Make sure the passed title matches the article title, and the tool should be able to find the wikidata entry.
|
||||||
The next section will go over what these infobox templates files are, where you can find them on wikipedia, and how you can customize them locally for your wikidata analysis.
|
The next section will go over what these infobox templates files are, where you can find them on wikipedia, and how you can customize them locally for your wikidata analysis.
|
||||||
|
|
||||||
### Interpretation of output
|
|
||||||
The left column of the table first lists all the properties that the infobox requires.
|
|
||||||
The right column will display the same property name if it is present in wikidata.
|
|
||||||
If it is not present, the right column will be blank.
|
|
||||||
Remaining properties in wikidata that are not required by the infobox are trailed in the right column.
|
|
||||||
|
|
||||||
``` markdown
|
|
||||||
┌──────────────────┬─────────────────────┬───────────────┐
|
|
||||||
│ Infobox requires │ Wikidata Earth (Q2) │ Property name │
|
|
||||||
├──────────────────┼─────────────────────┼───────────────┤
|
|
||||||
│ P18 │ P18 │ image │
|
|
||||||
│ P170 │ P170 │ creator │
|
|
||||||
│ P571 │ P571 │ inception │
|
|
||||||
│ │ P31 │ │
|
|
||||||
│ │ P138 │ │
|
|
||||||
│ │ P361 │ │
|
|
||||||
│ │ .... │ │
|
|
||||||
└──────────────────┴─────────────────────┴───────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
This (shortened) example is complete, but if we were to try a different infobox template on the earth entry you can see that it is not a good fit.
|
|
||||||
To demonstrate, we can apply the software template to the earth entry:
|
|
||||||
|
|
||||||
``` markdown
|
|
||||||
┌──────────────────┬─────────────────────┬───────────────┐
|
|
||||||
│ Infobox requires │ Wikidata Earth (Q2) │ Property name │
|
|
||||||
├──────────────────┼─────────────────────┼───────────────┤
|
|
||||||
│ P18 │ P18 │ image │
|
|
||||||
│ P154 │ │ │
|
|
||||||
│ P170 │ P170 │ creator │
|
|
||||||
│ P178 │ │ │
|
|
||||||
│ P275 │ │ │
|
|
||||||
│ P277 │ │ │
|
|
||||||
│ P306 │ │ │
|
|
||||||
│ P348 │ │ │
|
|
||||||
│ P400 │ │ │
|
|
||||||
│ P548 │ │ │
|
|
||||||
│ P571 │ P571 │ inception │
|
|
||||||
│ P577 │ │ │
|
|
||||||
│ P856 │ │ │
|
|
||||||
│ P1324 │ │ │
|
|
||||||
│ P2096 │ │ │
|
|
||||||
│ │ P10 │ │
|
|
||||||
│ │ P31 │ │
|
|
||||||
│ │ P138 │ │
|
|
||||||
│ │ .... │ │
|
|
||||||
└──────────────────┴─────────────────────┴───────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
## Templates
|
## Templates
|
||||||
The wikipedia sites vary a lot when it comes to templates across the different languages.
|
The wikipedia sites vary a lot when it comes to templates across the different languages.
|
||||||
The goal of this tool is to be universal, but these templates have not been standardized as far as I am aware.
|
The goal of this tool is to be universal, but these templates have not been standardized as far as I am aware.
|
||||||
@@ -94,7 +45,7 @@ Then you can add the following line to that file locally:
|
|||||||
|
|
||||||
The program ignores what you put at the `...`.
|
The program ignores what you put at the `...`.
|
||||||
It permits templates that include a listing of wikidata entries for their templates.
|
It permits templates that include a listing of wikidata entries for their templates.
|
||||||
As an example, this is the case on the following software template on the Dutch wikipedia (the first word means "uses", it can be found here: https://nl.wikipedia.org/wiki/Sjabloon:Infobox_software):
|
As an example, this is the case on the following template on the Dutch wikipedia (the first word means "uses"):
|
||||||
|
|
||||||
``` text
|
``` text
|
||||||
{{Gebruikt Wikidata|P18|P154|P170|P178|P275|P277|P306|P348|P400|P548|P571|P577|P856|P1324|P2096}}
|
{{Gebruikt Wikidata|P18|P154|P170|P178|P275|P277|P306|P348|P400|P548|P571|P577|P856|P1324|P2096}}
|
||||||
@@ -102,36 +53,3 @@ As an example, this is the case on the following software template on the Dutch
|
|||||||
|
|
||||||
Ideally, these used properties would be discovered by use, but I have not found a way to do that universally.
|
Ideally, these used properties would be discovered by use, but I have not found a way to do that universally.
|
||||||
Besides, you only have to do this once, and this way you can also customize the properties to look for.
|
Besides, you only have to do this once, and this way you can also customize the properties to look for.
|
||||||
|
|
||||||
It is often the case that there are similar properties on a template, where to goal is that just one is filled in.
|
|
||||||
To go back to the software example, let's take the Dutch wikipedia article for GitLab as an example.
|
|
||||||
|
|
||||||
``` markdown
|
|
||||||
┌──────────────────┬─────────────────────────────┬─────────────────────────────┐
|
|
||||||
│ Infobox requires │ Wikidata GitLab (Q16639197) │ Property names │
|
|
||||||
├──────────────────┼─────────────────────────────┼─────────────────────────────┤
|
|
||||||
│ P18 │ P18 │ image │
|
|
||||||
│ P154 │ P154 │ logo image │
|
|
||||||
│ P170 │ │ creator │
|
|
||||||
│ P178 │ P178 │ developer │
|
|
||||||
│ P275 │ P275 │ copyright license │
|
|
||||||
│ P277 │ P277 │ programmed in │
|
|
||||||
│ P306 │ P306 │ operating system │
|
|
||||||
│ P348 │ P348 │ software version identifier │
|
|
||||||
│ P400 │ │ platform │
|
|
||||||
│ P548 │ │ version type │
|
|
||||||
│ P571 │ P571 │ inception │
|
|
||||||
│ P577 │ │ publication date │
|
|
||||||
│ P856 │ P856 │ official website │
|
|
||||||
│ P1324 │ P1324 │ source code repository URL │
|
|
||||||
│ P2096 │ │ media legend │
|
|
||||||
│ │ P10 │ │
|
|
||||||
│ │ P17 │ │
|
|
||||||
│ │ P31 │ │
|
|
||||||
│ │ .... │ │
|
|
||||||
└──────────────────┴─────────────────────────────┴─────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
Here you can see that the infobox template is sufficiently filled in.
|
|
||||||
And yes, there are some fields missing in the wikidata that the template expects, but those are covered by other properties.
|
|
||||||
This tool should give you the ability to quickly find the missing properties to be entered in wikidata.
|
|
||||||
|
|||||||
+42
-37
@@ -1,50 +1,36 @@
|
|||||||
#[doc = include_str!("../README.md")]
|
#[doc = include_str!("../README.md")]
|
||||||
pub mod wikipedia_infobox_analyzer {
|
pub mod wikipedia_infobox_analyzer {
|
||||||
|
use ascii_table::{Align, AsciiTable};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
|
||||||
/// Looks for a used template that does lists wikidata properties listing
|
/// Looks for a used template that does lists wikidata properties listing
|
||||||
pub fn extract_used_properties_from_template(template: String) -> Vec<u64> {
|
pub fn extract_used_properties_from_template(template: String) -> Vec<String> {
|
||||||
let mut lines = template.lines();
|
let mut lines = template.lines();
|
||||||
let used = lines
|
let used = lines
|
||||||
.find(|line| line.starts_with("{{") && line.contains("Wikidata|"))
|
.find(|line| line.starts_with("{{") && line.contains("Wikidata|"))
|
||||||
.expect("Template should have a line declaring which properties are used");
|
.expect("Template should have a line declaring which properties are used");
|
||||||
|
|
||||||
// The properties are listed inside the template with their number: P1|P2|P...
|
// The properties are listed inside the template with their number: P1|P2|P...
|
||||||
let re = Regex::new(r"P(\d+)").unwrap();
|
let re = Regex::new(r"P\d+").unwrap();
|
||||||
|
|
||||||
// Find all matches and collect the properties into vector
|
// Find all matches and collect the properties into vector
|
||||||
re.captures_iter(used)
|
re.find_iter(used)
|
||||||
.map(|m| m[1].parse().unwrap())
|
.map(|m| m.as_str().to_owned())
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Properties will be displayed in the right column if they are in the list for that column
|
/// Creates an ascii infobox out of the properties
|
||||||
///
|
pub fn format_infobox_from_used_properties(name: String, properties: Vec<u64>) -> String {
|
||||||
/// All items in the left columns will be displayed
|
let mut ascii_table = AsciiTable::default();
|
||||||
/// The remaining items in the right columns not in the left columns will trail in the right column
|
ascii_table.set_max_width(26);
|
||||||
/// ```md
|
ascii_table
|
||||||
/// ┌───────┬───────┐
|
.column(0)
|
||||||
/// │ 1 │ 1 │
|
.set_header(name)
|
||||||
/// │ 3 │ │
|
.set_align(Align::Left);
|
||||||
/// │ │ 2 │
|
|
||||||
/// │ │ 4 │
|
let data: Vec<Vec<String>> = properties.iter().map(|v| vec![format!("P{v}")]).collect();
|
||||||
/// └───────┴───────┘
|
ascii_table.format(data)
|
||||||
/// ```
|
|
||||||
/// So for example, [1, 3] and [1, 2, 4] become [1, 2, 3, 4], as shown above
|
|
||||||
pub fn join_columns<T: Copy + Eq>(vec1: &[T], vec2: &[T]) -> Vec<(Option<T>, Option<T>)> {
|
|
||||||
vec1.iter()
|
|
||||||
.flat_map(|n1| {
|
|
||||||
if let Some(&n2) = vec2.iter().find(|&x| x == n1) {
|
|
||||||
std::iter::once((Some(*n1), Some(n2)))
|
|
||||||
} else {
|
|
||||||
std::iter::once((Some(*n1), None))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.chain(vec2.iter()
|
|
||||||
.filter(|&n2| !vec1.contains(n2))
|
|
||||||
.map(|n2| (None, Some(*n2))))
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fetches the wikidata label for the wiki property
|
/// Fetches the wikidata label for the wiki property
|
||||||
@@ -100,7 +86,7 @@ pub mod wikipedia_infobox_analyzer {
|
|||||||
.collect::<Vec<u64>>()
|
.collect::<Vec<u64>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fetches the wikidata identifier associated with the wikipedia article
|
/// Fetch the wikidata identifier associated with the wikipedia article
|
||||||
pub fn fetch_wiki_item_by_article_title(title: String, language_code: String) -> u64 {
|
pub fn fetch_wiki_item_by_article_title(title: String, language_code: String) -> u64 {
|
||||||
let uri = format!("https://{}.wikipedia.org/w/api.php?action=query&prop=pageprops&format=json&titles={}", &language_code, &title);
|
let uri = format!("https://{}.wikipedia.org/w/api.php?action=query&prop=pageprops&format=json&titles={}", &language_code, &title);
|
||||||
|
|
||||||
@@ -118,7 +104,7 @@ pub mod wikipedia_infobox_analyzer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests_lib {
|
mod tests {
|
||||||
use super::wikipedia_infobox_analyzer::*;
|
use super::wikipedia_infobox_analyzer::*;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
@@ -130,9 +116,9 @@ mod tests_lib {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
extract_used_properties_from_template(contents),
|
extract_used_properties_from_template(contents),
|
||||||
vec![
|
vec![
|
||||||
18, 154, 1128, 4103, 2139, 2295, 2226, 856, 169,
|
"P18", "P154", "P1128", "P4103", "P2139", "P2295", "P2226", "P856", "P169",
|
||||||
1448, 159, 749, 355, 17, 1056, 452, 576, 112, 127,
|
"P1448", "P159", "P749", "P355", "P17", "P1056", "P452", "P576", "P112", "P127",
|
||||||
856, 2096
|
"P856", "P2096"
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -142,8 +128,27 @@ mod tests_lib {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
extract_used_properties_from_template(contents),
|
extract_used_properties_from_template(contents),
|
||||||
vec![
|
vec![
|
||||||
18, 154, 170, 178, 275, 277, 306, 348, 400, 548,
|
"P18", "P154", "P170", "P178", "P275", "P277", "P306", "P348", "P400", "P548",
|
||||||
571, 577, 856, 1324, 2096
|
"P571", "P577", "P856", "P1324", "P2096"
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_format_infobox_from_used_properties() {
|
||||||
|
assert_eq!(
|
||||||
|
format_infobox_from_used_properties(
|
||||||
|
"Earth".to_string(),
|
||||||
|
vec![31, 361, 571]
|
||||||
|
),
|
||||||
|
concat![
|
||||||
|
"┌───────┐\n",
|
||||||
|
"│ Earth │\n", // item identifier: Q2
|
||||||
|
"├───────┤\n",
|
||||||
|
"│ P31 │\n", // property: instance of
|
||||||
|
"│ P361 │\n", // property: part of
|
||||||
|
"│ P571 │\n", // property: inception
|
||||||
|
"└───────┘\n"
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-220
@@ -1,12 +1,5 @@
|
|||||||
use wikipedia_infobox_analyzer::wikipedia_infobox_analyzer::*;
|
|
||||||
|
|
||||||
use ascii_table::{Align, AsciiTable};
|
|
||||||
use dirs_next::cache_dir;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::fs;
|
|
||||||
use std::io;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
use wikipedia_infobox_analyzer::wikipedia_infobox_analyzer::*;
|
||||||
|
|
||||||
#[derive(StructOpt)]
|
#[derive(StructOpt)]
|
||||||
struct Cli {
|
struct Cli {
|
||||||
@@ -18,227 +11,22 @@ struct Cli {
|
|||||||
infobox_template_file: std::path::PathBuf,
|
infobox_template_file: std::path::PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
struct StorageDisk {
|
let args = Cli::from_args();
|
||||||
base_path: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StorageDisk {
|
|
||||||
fn new(app_name: &str) -> std::io::Result<Self> {
|
|
||||||
// Get OS-specific cache directory, otherwise use a fallback location
|
|
||||||
let mut path = match cache_dir() {
|
|
||||||
Some(path) => path,
|
|
||||||
None => PathBuf::from("./.cache"),
|
|
||||||
};
|
|
||||||
|
|
||||||
path.push(app_name);
|
|
||||||
match fs::create_dir_all(&path) {
|
|
||||||
Ok(_) => Ok(StorageDisk { base_path: path }),
|
|
||||||
Err(_) => panic!("Failed to create storage directory: {path:?}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn store_data(&self, filename: String, data: String) -> std::io::Result<()> {
|
|
||||||
let file_path = self.base_path.join(filename);
|
|
||||||
fs::write(file_path, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_data(&self, filename: String) -> std::io::Result<String> {
|
|
||||||
let file_path = self.base_path.join(filename);
|
|
||||||
fs::read_to_string(file_path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Cache {
|
|
||||||
file_name: String,
|
|
||||||
storage: StorageDisk,
|
|
||||||
property_labels: HashMap<String, String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Cache {
|
|
||||||
fn new(file_name: String, storage: StorageDisk) -> std::io::Result<Self> {
|
|
||||||
let property_labels: HashMap<String, String> = match storage.read_data(file_name.clone()) {
|
|
||||||
Ok(raw_data) => {
|
|
||||||
serde_json::from_str(&raw_data)?
|
|
||||||
}
|
|
||||||
Err(err) if err.kind() == io::ErrorKind::NotFound => {
|
|
||||||
println!("No cache found, starting an empty cache");
|
|
||||||
|
|
||||||
HashMap::new()
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
println!("Unknown error occurred, exiting gracefully");
|
|
||||||
return Err(err)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let cache = Cache {
|
|
||||||
file_name,
|
|
||||||
storage,
|
|
||||||
property_labels
|
|
||||||
};
|
|
||||||
cache.sync_to_storage()?;
|
|
||||||
|
|
||||||
Ok(cache)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sync_to_storage(&self) -> std::io::Result<()> {
|
|
||||||
let data = serde_json::to_string(&self.property_labels)?;
|
|
||||||
self.storage.store_data(self.file_name.clone(), data)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn delete(&mut self) -> std::io::Result<()> {
|
|
||||||
fs::remove_file(self.storage.base_path.join(self.file_name.clone()))?;
|
|
||||||
self.property_labels = HashMap::new();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn lookup_property_name(&mut self, pid: u64) -> std::io::Result<String> {
|
|
||||||
let key = format!("P{pid}");
|
|
||||||
if let Some(name) = self.property_labels.get(&key) {
|
|
||||||
return Ok(name.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the property name cannot be found, fetch the name
|
|
||||||
let name = fetch_name_for_wiki_property(pid).await;
|
|
||||||
|
|
||||||
// Update cache with new value, so it can be used the next time
|
|
||||||
self.property_labels.insert(key, name.clone());
|
|
||||||
self.sync_to_storage()?;
|
|
||||||
|
|
||||||
Ok(name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates an ascii table out of the properties
|
|
||||||
fn format_property_usage(name: String, qid: String, properties_template: Vec<u64>, properties_item: Vec<u64>, properties_labels: Vec<String>) -> String {
|
|
||||||
let mut ascii_table = AsciiTable::default();
|
|
||||||
ascii_table.set_max_width(80);
|
|
||||||
ascii_table
|
|
||||||
.column(0)
|
|
||||||
.set_header("Infobox requires")
|
|
||||||
.set_align(Align::Left);
|
|
||||||
|
|
||||||
ascii_table
|
|
||||||
.column(1)
|
|
||||||
.set_header(format!("Wikidata {name} ({qid})"))
|
|
||||||
.set_align(Align::Left);
|
|
||||||
|
|
||||||
ascii_table
|
|
||||||
.column(2)
|
|
||||||
.set_header("Property name")
|
|
||||||
.set_align(Align::Left);
|
|
||||||
|
|
||||||
// Display the property columns as a table
|
|
||||||
let data: Vec<Vec<String>> = join_columns(&properties_template, &properties_item)
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(index, &m)| {
|
|
||||||
let (v1, v2) = m;
|
|
||||||
let property_name = if index >= properties_template.len() {
|
|
||||||
"".to_string()
|
|
||||||
} else {
|
|
||||||
properties_labels[index].clone()
|
|
||||||
};
|
|
||||||
vec![ v1.map_or("".to_owned(), |v| format!("P{v}").to_owned())
|
|
||||||
, v2.map_or("".to_owned(), |v| format!("P{v}").to_owned())
|
|
||||||
, property_name]
|
|
||||||
}
|
|
||||||
).collect();
|
|
||||||
ascii_table.format(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
// Initialize cache and storage
|
|
||||||
let storage = StorageDisk::new("wikipedia-infobox-analyzer")?;
|
|
||||||
let mut cache = Cache::new("property_labels.cache".to_string(), storage)?;
|
|
||||||
|
|
||||||
// Input to the program
|
// Input to the program
|
||||||
let args = Cli::from_args();
|
|
||||||
let title = args.wikipedia_article_title;
|
let title = args.wikipedia_article_title;
|
||||||
let language = args.wikipedia_language_code;
|
let language = args.wikipedia_language_code;
|
||||||
let template = std::fs::read_to_string(&args.infobox_template_file)?;
|
let _template = std::fs::read_to_string(&args.infobox_template_file)?;
|
||||||
|
|
||||||
let qid = fetch_wiki_item_by_article_title(title.clone(), language.clone());
|
let qid = fetch_wiki_item_by_article_title(title.clone(), language.clone());
|
||||||
let properties_template = extract_used_properties_from_template(template);
|
|
||||||
let properties_item = fetch_properties_for_wiki_item(qid);
|
|
||||||
let mut properties_labels: Vec<String> = vec![];
|
|
||||||
|
|
||||||
for property in &properties_template {
|
let properties = fetch_properties_for_wiki_item(qid);
|
||||||
let name = cache.lookup_property_name(*property).await?;
|
let table = format_infobox_from_used_properties(title.clone(), properties);
|
||||||
properties_labels.push(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
let table = format_property_usage(
|
println!("Qid: {qid}");
|
||||||
title.clone(),
|
println!("Template file exists");
|
||||||
format!("Q{qid}"),
|
|
||||||
properties_template,
|
|
||||||
properties_item,
|
|
||||||
properties_labels,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Output of the program
|
|
||||||
println!("{table}");
|
println!("{table}");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests_main {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_cache() {
|
|
||||||
let storage = StorageDisk::new("wikipedia-infobox-analyzer").unwrap();
|
|
||||||
let mut cache = Cache::new("property_labels.test.cache".to_string(), storage.clone()).unwrap();
|
|
||||||
let _ = cache.delete();
|
|
||||||
|
|
||||||
// Cache should be clear after deletion
|
|
||||||
assert_eq!(cache.property_labels, HashMap::new());
|
|
||||||
|
|
||||||
let name = cache.lookup_property_name(31).await.unwrap();
|
|
||||||
assert_eq!(name, "instance of");
|
|
||||||
|
|
||||||
// Cache should store values that are looked up
|
|
||||||
assert_eq!(cache.property_labels.get("P31").unwrap(), "instance of");
|
|
||||||
|
|
||||||
// Cache should be persistent if the same file is passed
|
|
||||||
let mut cache_new = Cache::new("property_labels.test.cache".to_string(), storage.clone()).unwrap();
|
|
||||||
assert_eq!(cache_new.property_labels.get("P31").unwrap(), "instance of");
|
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
let _ = cache_new.delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_format_property_usage() {
|
|
||||||
assert_eq!(
|
|
||||||
format_property_usage(
|
|
||||||
"Earth".to_string(),
|
|
||||||
"Q2".to_string(),
|
|
||||||
vec![18, 170, 571],
|
|
||||||
vec![18, 31, 138, 170, 361, 571],
|
|
||||||
vec![ "image".to_string()
|
|
||||||
, "creator".to_string()
|
|
||||||
, "inception".to_string()
|
|
||||||
]
|
|
||||||
),
|
|
||||||
|
|
||||||
concat![
|
|
||||||
"┌──────────────────┬─────────────────────┬───────────────┐\n",
|
|
||||||
"│ Infobox requires │ Wikidata Earth (Q2) │ Property name │\n",
|
|
||||||
"├──────────────────┼─────────────────────┼───────────────┤\n",
|
|
||||||
"│ P18 │ P18 │ image │\n",
|
|
||||||
"│ P170 │ P170 │ creator │\n",
|
|
||||||
"│ P571 │ P571 │ inception │\n",
|
|
||||||
"│ │ P31 │ │\n",
|
|
||||||
"│ │ P138 │ │\n",
|
|
||||||
"│ │ P361 │ │\n",
|
|
||||||
"└──────────────────┴─────────────────────┴───────────────┘\n",
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user