1use std::collections::HashMap;
9
10use magnus::prelude::*;
11use magnus::{Error, Ruby, function, method};
12
13use lindera::dictionary::Metadata;
14use lindera_binding_core::CoreMetadata;
15
16#[magnus::wrap(class = "Lindera::Metadata", free_immediately, size)]
21#[derive(Debug, Clone)]
22pub struct RbMetadata {
23 inner: CoreMetadata,
25}
26
27impl RbMetadata {
28 #[allow(clippy::too_many_arguments)]
38 fn new(
39 name: Option<String>,
40 encoding: Option<String>,
41 default_word_cost: Option<i16>,
42 default_left_context_id: Option<u16>,
43 default_right_context_id: Option<u16>,
44 default_field_value: Option<String>,
45 flexible_csv: Option<bool>,
46 skip_invalid_cost_or_id: Option<bool>,
47 normalize_details: Option<bool>,
48 ) -> Self {
49 RbMetadata {
50 inner: CoreMetadata::new(
51 name,
52 encoding,
53 default_word_cost,
54 default_left_context_id,
55 default_right_context_id,
56 default_field_value,
57 flexible_csv,
58 skip_invalid_cost_or_id,
59 normalize_details,
60 None,
61 None,
62 ),
63 }
64 }
65
66 fn create_default() -> Self {
72 RbMetadata::new(None, None, None, None, None, None, None, None, None)
73 }
74
75 fn from_json_file(path: String) -> Result<Self, Error> {
85 let ruby = Ruby::get().expect("Ruby runtime not initialized");
86
87 let json_str = std::fs::read_to_string(&path).map_err(|e| {
88 Error::new(
89 ruby.exception_io_error(),
90 format!("Failed to read file: {e}"),
91 )
92 })?;
93
94 let metadata: Metadata = serde_json::from_str(&json_str).map_err(|e| {
95 Error::new(
96 ruby.exception_arg_error(),
97 format!("Failed to parse JSON: {e}"),
98 )
99 })?;
100
101 Ok(metadata.into())
102 }
103
104 fn name(&self) -> String {
106 self.inner.name.clone()
107 }
108
109 fn encoding(&self) -> String {
111 self.inner.encoding.clone()
112 }
113
114 fn default_word_cost(&self) -> i16 {
116 self.inner.default_word_cost
117 }
118
119 fn default_left_context_id(&self) -> u16 {
121 self.inner.default_left_context_id
122 }
123
124 fn default_right_context_id(&self) -> u16 {
126 self.inner.default_right_context_id
127 }
128
129 fn default_field_value(&self) -> String {
131 self.inner.default_field_value.clone()
132 }
133
134 fn flexible_csv(&self) -> bool {
136 self.inner.flexible_csv
137 }
138
139 fn skip_invalid_cost_or_id(&self) -> bool {
141 self.inner.skip_invalid_cost_or_id
142 }
143
144 fn normalize_details(&self) -> bool {
146 self.inner.normalize_details
147 }
148
149 fn to_hash(&self) -> HashMap<String, String> {
155 let mut dict = HashMap::new();
156 dict.insert("name".to_string(), self.inner.name.clone());
157 dict.insert("encoding".to_string(), self.inner.encoding.clone());
158 dict.insert(
159 "default_word_cost".to_string(),
160 self.inner.default_word_cost.to_string(),
161 );
162 dict.insert(
163 "default_left_context_id".to_string(),
164 self.inner.default_left_context_id.to_string(),
165 );
166 dict.insert(
167 "default_right_context_id".to_string(),
168 self.inner.default_right_context_id.to_string(),
169 );
170 dict.insert(
171 "default_field_value".to_string(),
172 self.inner.default_field_value.clone(),
173 );
174 dict.insert(
175 "flexible_csv".to_string(),
176 self.inner.flexible_csv.to_string(),
177 );
178 dict.insert(
179 "skip_invalid_cost_or_id".to_string(),
180 self.inner.skip_invalid_cost_or_id.to_string(),
181 );
182 dict.insert(
183 "normalize_details".to_string(),
184 self.inner.normalize_details.to_string(),
185 );
186 dict.insert(
187 "dictionary_schema_fields".to_string(),
188 self.inner.dictionary_schema.fields().join(","),
189 );
190 dict.insert(
191 "user_dictionary_schema_fields".to_string(),
192 self.inner.user_dictionary_schema.fields().join(","),
193 );
194 dict
195 }
196
197 fn to_s(&self) -> String {
199 format!(
200 "Metadata(name='{}', encoding='{}')",
201 self.inner.name, self.inner.encoding,
202 )
203 }
204
205 fn inspect(&self) -> String {
207 format!(
208 "#<Lindera::Metadata: name='{}', encoding='{}', schema_fields={}>",
209 self.inner.name,
210 self.inner.encoding,
211 self.inner.dictionary_schema.field_count()
212 )
213 }
214}
215
216impl From<RbMetadata> for Metadata {
217 fn from(metadata: RbMetadata) -> Self {
218 metadata.inner.into()
219 }
220}
221
222impl From<Metadata> for RbMetadata {
223 fn from(metadata: Metadata) -> Self {
224 RbMetadata {
225 inner: CoreMetadata::from(metadata),
226 }
227 }
228}
229
230pub fn define(ruby: &Ruby, module: &magnus::RModule) -> Result<(), Error> {
241 let metadata_class = module.define_class("Metadata", ruby.class_object())?;
242 metadata_class.define_singleton_method("new", function!(RbMetadata::new, 9))?;
243 metadata_class
244 .define_singleton_method("create_default", function!(RbMetadata::create_default, 0))?;
245 metadata_class
246 .define_singleton_method("from_json_file", function!(RbMetadata::from_json_file, 1))?;
247 metadata_class.define_method("name", method!(RbMetadata::name, 0))?;
248 metadata_class.define_method("encoding", method!(RbMetadata::encoding, 0))?;
249 metadata_class.define_method(
250 "default_word_cost",
251 method!(RbMetadata::default_word_cost, 0),
252 )?;
253 metadata_class.define_method(
254 "default_left_context_id",
255 method!(RbMetadata::default_left_context_id, 0),
256 )?;
257 metadata_class.define_method(
258 "default_right_context_id",
259 method!(RbMetadata::default_right_context_id, 0),
260 )?;
261 metadata_class.define_method(
262 "default_field_value",
263 method!(RbMetadata::default_field_value, 0),
264 )?;
265 metadata_class.define_method("flexible_csv", method!(RbMetadata::flexible_csv, 0))?;
266 metadata_class.define_method(
267 "skip_invalid_cost_or_id",
268 method!(RbMetadata::skip_invalid_cost_or_id, 0),
269 )?;
270 metadata_class.define_method(
271 "normalize_details",
272 method!(RbMetadata::normalize_details, 0),
273 )?;
274 metadata_class.define_method("to_hash", method!(RbMetadata::to_hash, 0))?;
275 metadata_class.define_method("to_h", method!(RbMetadata::to_hash, 0))?;
276 metadata_class.define_method("to_s", method!(RbMetadata::to_s, 0))?;
277 metadata_class.define_method("inspect", method!(RbMetadata::inspect, 0))?;
278
279 Ok(())
280}
281
282#[cfg(test)]
283mod tests {
284 use super::*;
285 use lindera_binding_core::CoreSchema;
286
287 #[test]
288 fn test_rb_metadata_to_lindera_metadata() {
289 let rb_metadata = RbMetadata {
290 inner: CoreMetadata::new(
291 Some("test_dict".to_string()),
292 Some("EUC-JP".to_string()),
293 Some(-5000),
294 Some(100),
295 Some(200),
296 Some("N/A".to_string()),
297 Some(true),
298 Some(true),
299 Some(true),
300 Some(CoreSchema::new(vec![
301 "surface".to_string(),
302 "cost".to_string(),
303 ])),
304 Some(CoreSchema::new(vec!["surface".to_string()])),
305 ),
306 };
307
308 let lindera_metadata: Metadata = rb_metadata.into();
309 assert_eq!(lindera_metadata.name, "test_dict");
310 assert_eq!(lindera_metadata.encoding, "EUC-JP");
311 assert_eq!(lindera_metadata.default_word_cost, -5000);
312 assert_eq!(lindera_metadata.default_left_context_id, 100);
313 assert_eq!(lindera_metadata.default_right_context_id, 200);
314 assert_eq!(lindera_metadata.default_field_value, "N/A");
315 assert!(lindera_metadata.flexible_csv);
316 assert!(lindera_metadata.skip_invalid_cost_or_id);
317 assert!(lindera_metadata.normalize_details);
318 assert_eq!(lindera_metadata.dictionary_schema.get_all_fields().len(), 2);
319 assert_eq!(
320 lindera_metadata
321 .user_dictionary_schema
322 .get_all_fields()
323 .len(),
324 1
325 );
326 }
327
328 #[test]
329 fn test_lindera_metadata_to_rb_metadata() {
330 let dict_schema =
331 lindera::dictionary::Schema::new(vec!["surface".to_string(), "cost".to_string()]);
332 let user_schema =
333 lindera::dictionary::Schema::new(vec!["surface".to_string(), "reading".to_string()]);
334
335 let lindera_metadata = Metadata::new(
336 "my_dict".to_string(),
337 "UTF-8".to_string(),
338 -8000,
339 500,
340 600,
341 "?".to_string(),
342 false,
343 true,
344 false,
345 dict_schema,
346 user_schema,
347 );
348
349 let rb_metadata: RbMetadata = lindera_metadata.into();
350 assert_eq!(rb_metadata.name(), "my_dict");
351 assert_eq!(rb_metadata.encoding(), "UTF-8");
352 assert_eq!(rb_metadata.default_word_cost(), -8000);
353 assert_eq!(rb_metadata.default_left_context_id(), 500);
354 assert_eq!(rb_metadata.default_right_context_id(), 600);
355 assert_eq!(rb_metadata.default_field_value(), "?");
356 assert!(!rb_metadata.flexible_csv());
357 assert!(rb_metadata.skip_invalid_cost_or_id());
358 assert!(!rb_metadata.normalize_details());
359 assert_eq!(rb_metadata.inner.dictionary_schema.field_count(), 2);
360 assert_eq!(rb_metadata.inner.user_dictionary_schema.field_count(), 2);
361 }
362
363 #[test]
364 fn test_rb_metadata_defaults() {
365 let rb_metadata = RbMetadata::create_default();
366 assert_eq!(rb_metadata.name(), "default");
367 assert_eq!(rb_metadata.encoding(), "UTF-8");
368 assert_eq!(rb_metadata.default_word_cost(), -10000);
369 assert_eq!(rb_metadata.default_left_context_id(), 1288);
370 assert_eq!(rb_metadata.default_right_context_id(), 1288);
371 assert_eq!(rb_metadata.default_field_value(), "*");
372 assert!(!rb_metadata.flexible_csv());
373 assert_eq!(rb_metadata.inner.dictionary_schema.field_count(), 13);
374 assert_eq!(rb_metadata.inner.user_dictionary_schema.field_count(), 3);
375 }
376
377 #[test]
378 fn test_rb_metadata_roundtrip() {
379 let rb_metadata = RbMetadata::create_default();
380 let lindera: Metadata = rb_metadata.into();
381 let back: RbMetadata = lindera.into();
382 assert_eq!(back.name(), "default");
383 assert_eq!(back.encoding(), "UTF-8");
384 assert_eq!(back.default_word_cost(), -10000);
385 assert_eq!(back.default_left_context_id(), 1288);
386 assert_eq!(back.default_right_context_id(), 1288);
387 assert_eq!(back.default_field_value(), "*");
388 assert_eq!(back.inner.dictionary_schema.field_count(), 13);
389 assert_eq!(back.inner.user_dictionary_schema.field_count(), 3);
390 }
391}