1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
|
//! The data model shared by both sources (DLE and rae-api) and both output
//! formats (terminal and JSON).
use serde::{Deserialize, Serialize};
/// A dictionary entry: everything known about a word.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Entry {
/// Headword as the dictionary writes it (may carry a superscript).
pub word: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub etymology: Option<String>,
pub senses: Vec<Sense>,
/// Set phrases and expressions: «ni hablar», «vino de coco»…
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub locutions: Vec<Locution>,
#[serde(skip_serializing_if = "Option::is_none")]
pub conjugation: Option<Conjugation>,
/// Where it came from: `dle` or `rae-api`.
pub source: String,
}
impl Entry {
/// Synonyms of all senses, without repeats and in order of appearance.
pub fn all_synonyms(&self) -> Vec<String> {
dedup(self.senses.iter().flat_map(|s| s.synonyms.iter()))
}
pub fn all_antonyms(&self) -> Vec<String> {
dedup(self.senses.iter().flat_map(|s| s.antonyms.iter()))
}
pub fn has_synonyms(&self) -> bool {
self.senses.iter().any(|s| !s.synonyms.is_empty())
}
pub fn has_antonyms(&self) -> bool {
self.senses.iter().any(|s| !s.antonyms.is_empty())
}
}
fn dedup<'a>(it: impl Iterator<Item = &'a String>) -> Vec<String> {
let mut out: Vec<String> = Vec::new();
for w in it {
if !out.iter().any(|e| e == w) {
out.push(w.clone());
}
}
out
}
/// A numbered sense.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Sense {
/// Sense number, without the dot: `"1"`.
#[serde(skip_serializing_if = "Option::is_none")]
pub number: Option<String>,
/// Grammar and usage marks (`f.`, `intr.`, `p. us.`) with their expanded
/// meaning, which the DLE keeps in the `title` attribute.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub grammar: Vec<Abbr>,
/// The definition text, without the number or the marks.
pub text: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub examples: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub synonyms: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub antonyms: Vec<String>,
}
/// A dictionary abbreviation together with its expanded form.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Abbr {
/// As printed: `f.`
pub short: String,
/// As the dictionary explains it: `nombre femenino`
pub long: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Locution {
pub phrase: String,
pub senses: Vec<Sense>,
}
/// The full conjugation of a verb, grouped the way the DLE groups it.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Conjugation {
/// «Conjugación de "hablar"»
pub title: String,
pub groups: Vec<ConjGroup>,
}
/// A verb mood: «Modo indicativo», «Formas no personales»…
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConjGroup {
pub title: String,
pub tenses: Vec<Tense>,
}
/// A verb tense with its forms, each one with its pronoun
/// («yo hablo», «vos hablás»).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Tense {
pub name: String,
pub forms: Vec<String>,
}
#[cfg(test)]
mod tests {
use super::*;
fn sense(syn: &[&str], ant: &[&str]) -> Sense {
Sense {
synonyms: syn.iter().map(|s| s.to_string()).collect(),
antonyms: ant.iter().map(|s| s.to_string()).collect(),
..Default::default()
}
}
fn entry(senses: Vec<Sense>) -> Entry {
Entry {
word: "casa".into(),
etymology: None,
senses,
locutions: vec![],
conjugation: None,
source: "dle".into(),
}
}
#[test]
fn collects_synonyms_across_senses_without_repeating() {
let e = entry(vec![
sense(&["vivienda", "hogar"], &[]),
sense(&["hogar", "morada"], &[]),
]);
assert_eq!(e.all_synonyms(), ["vivienda", "hogar", "morada"]);
assert!(e.has_synonyms());
assert!(!e.has_antonyms());
}
#[test]
fn reports_no_synonyms_for_an_entry_without_them() {
let e = entry(vec![sense(&[], &[])]);
assert!(e.all_synonyms().is_empty());
assert!(!e.has_synonyms());
}
}
|