1#![deny(missing_docs)]
214
215#[macro_use]
216extern crate serde_derive;
217#[macro_use]
218extern crate derive_error;
219
220use dynfmt::{Argument, Format, FormatArgs, PythonFormat};
221use libc_strftime::strftime_local;
222#[allow(unused_imports)]
223use serde::Deserialize;
224use std::collections::HashMap;
225use std::convert::TryFrom;
226use std::string::ToString;
227
228#[derive(Debug, Error)]
230pub enum Error {
231 #[error(msg_embedded, no_from, non_std)]
233 FormatError(String),
234 #[error(non_std, no_from, display = "missing join separator")]
236 MissingJoinSeparator,
237}
238
239#[derive(Deserialize, Clone, Debug)]
241pub struct SerdeGetText {
242 #[serde(flatten)]
243 value: Value,
244 #[serde(skip)]
246 pub args: HashMap<String, String>,
247}
248
249impl TryFrom<SerdeGetText> for String {
250 type Error = Error;
251
252 fn try_from(x: SerdeGetText) -> Result<String, Error> {
253 x.value.try_into_string(&x.args)
254 }
255}
256
257impl TryFrom<Box<SerdeGetText>> for String {
258 type Error = Error;
259
260 fn try_from(x: Box<SerdeGetText>) -> Result<String, Error> {
261 String::try_from(*x)
262 }
263}
264
265#[derive(Deserialize, Clone, Debug)]
266#[serde(untagged)]
267enum Value {
268 Text(String),
269 Integer(i64),
270 Float(f64),
271 Bool(bool),
272 Unit(()),
273 Datetime(DatetimeValue),
274 Array(Vec<Value>),
275 FormattedText {
276 text: String,
277 args: Option<Formatter>,
278 },
279 GetText {
280 gettext: ValueGetText,
281 args: Option<Formatter>,
282 },
283 NGetText {
284 ngettext: ValueNGetText,
285 args: Option<Formatter>,
286 },
287 PGetText {
288 pgettext: ValuePGetText,
289 args: Option<Formatter>,
290 },
291 DGetText {
292 dgettext: ValueDGetText,
293 args: Option<Formatter>,
294 },
295 DNGetText {
296 dngettext: ValueDNGetText,
297 args: Option<Formatter>,
298 },
299 NPGetText {
300 npgettext: ValueNPGetText,
301 args: Option<Formatter>,
302 },
303 DCNGetText {
304 dcngettext: ValueDCNGetText,
305 args: Option<Formatter>,
306 },
307}
308
309macro_rules! handle_gettext {
310 ($s:expr, $args:expr, $map:expr, $base_map:expr) => {{
311 Self::format(&$s.to_string(), $args, $map, $base_map)
312 }};
313}
314
315macro_rules! handle_plural {
316 ($s:expr, $args:expr, $map:expr, $base_map:expr) => {{
317 $map.reserve(match $args.as_ref() {
318 Some(Formatter::KeywordArgs(args)) => args.len() + 1,
319 _ => 1,
320 });
321 $map.insert("n".to_string(), $s.n.to_string());
322
323 Self::format(&$s.to_string(), $args, $map, $base_map)
324 }};
325}
326
327impl Value {
328 fn try_into_string(self, base_map: &HashMap<String, String>) -> Result<String, Error> {
329 let mut map = HashMap::new();
330
331 match self {
332 Value::Text(x) => Ok(x.to_string()),
333 Value::Integer(x) => Ok(x.to_string()),
334 Value::Float(x) => Ok(x.to_string()),
335 Value::Bool(x) => Ok(if x {
336 gettextrs::gettext(b"yes" as &[u8])
337 } else {
338 gettextrs::gettext(b"no" as &[u8])
339 }),
340 Value::Unit(()) => Ok(gettextrs::gettext(b"n/a" as &[u8])),
341 Value::Datetime(x) => Ok(x.to_string()),
342 Value::Array(xs) => Ok({
343 let mut it = xs.into_iter();
344 let sep: String = match it.next() {
345 Some(x) => x.try_into_string(base_map),
346 None => Err(Error::MissingJoinSeparator),
347 }?;
348
349 let mut vec: Vec<String> = Vec::new();
350
351 for value in it {
352 vec.push(value.try_into_string(base_map)?);
353 }
354
355 vec.join(&sep)
356 }),
357 Value::FormattedText { text, args } => Self::format(text.as_ref(), args, map, base_map),
358 Value::GetText { gettext, args } => handle_gettext!(gettext, args, map, base_map),
359 Value::NGetText { ngettext, args } => handle_plural!(ngettext, args, map, base_map),
360 Value::PGetText { pgettext, args } => handle_gettext!(pgettext, args, map, base_map),
361 Value::DGetText { dgettext, args } => handle_gettext!(dgettext, args, map, base_map),
362 Value::DNGetText { dngettext, args } => handle_plural!(dngettext, args, map, base_map),
363 Value::NPGetText { npgettext, args } => handle_plural!(npgettext, args, map, base_map),
364 Value::DCNGetText { dcngettext, args } => {
365 handle_plural!(dcngettext, args, map, base_map)
366 }
367 }
368 }
369
370 fn format(
371 message: &str,
372 formatter: Option<Formatter>,
373 mut map: HashMap<String, String>,
374 base_map: &HashMap<String, String>,
375 ) -> Result<String, Error> {
376 match formatter {
377 Some(Formatter::KeywordArgs(kwargs)) => {
378 for (key, value) in kwargs.into_iter() {
379 map.insert(key, value.try_into_string(base_map)?);
380 }
381
382 PythonFormat
383 .format(message, UnionMap::new(&map, base_map))
384 .map_err(|err| Error::FormatError(format!("{}", err)))
385 .map(|x| x.to_string())
386 }
387 Some(Formatter::PositionalArgs(args)) => PythonFormat
388 .format(
389 message,
390 args.into_iter()
391 .map(|x| x.try_into_string(base_map))
392 .collect::<Result<Vec<String>, _>>()?,
393 )
394 .map_err(|err| Error::FormatError(format!("{}", err)))
395 .map(|x| x.to_string()),
396 None => PythonFormat
397 .format(message, UnionMap::new(&map, base_map))
398 .map_err(|err| Error::FormatError(format!("{}", err)))
399 .map(|x| x.to_string()),
400 }
401 }
402}
403
404#[derive(Deserialize, Clone, Debug)]
405#[serde(untagged)]
406enum Formatter {
407 KeywordArgs(HashMap<String, Value>),
408 PositionalArgs(Vec<Value>),
409}
410
411struct UnionMap<'a>(&'a HashMap<String, String>, &'a HashMap<String, String>);
412
413impl<'a> UnionMap<'a> {
414 fn new(a: &'a HashMap<String, String>, b: &'a HashMap<String, String>) -> UnionMap<'a> {
415 UnionMap(a, b)
416 }
417}
418
419impl FormatArgs for UnionMap<'_> {
420 fn get_key(&self, key: &str) -> Result<Option<Argument<'_>>, ()> {
421 Ok(self
422 .0
423 .get(key)
424 .or_else(|| self.1.get(key))
425 .map(|x| x as Argument<'_>))
426 }
427}
428
429#[derive(Deserialize, Clone, Debug)]
430struct DatetimeValue {
431 strftime: String,
432 epoch: i64,
433}
434
435impl ToString for DatetimeValue {
436 fn to_string(&self) -> String {
437 strftime_local(&self.strftime, self.epoch)
438 }
439}
440
441#[derive(Deserialize, Clone, Debug)]
442struct ValueGetText(String);
443
444impl ToString for ValueGetText {
445 fn to_string(&self) -> String {
446 gettextrs::gettext(self.0.as_bytes())
447 }
448}
449
450#[derive(Deserialize, Clone, Debug)]
451struct ValueNGetText {
452 singular: String,
453 plural: String,
454 n: u32,
455}
456
457impl ToString for ValueNGetText {
458 fn to_string(&self) -> String {
459 gettextrs::ngettext(self.singular.as_bytes(), self.plural.as_bytes(), self.n)
460 }
461}
462
463#[derive(Deserialize, Clone, Debug)]
464struct ValuePGetText {
465 ctx: String,
466 msgid: String,
467}
468
469impl ToString for ValuePGetText {
470 fn to_string(&self) -> String {
471 gettextrs::pgettext(self.ctx.as_bytes(), self.msgid.as_bytes())
472 }
473}
474
475#[derive(Deserialize, Clone, Debug)]
476struct ValueDGetText {
477 domain: String,
478 msgid: String,
479}
480
481impl ToString for ValueDGetText {
482 fn to_string(&self) -> String {
483 gettextrs::dgettext(self.domain.as_bytes(), self.msgid.as_bytes())
484 }
485}
486
487#[derive(Deserialize, Clone, Debug)]
488struct ValueDNGetText {
489 domain: String,
490 singular: String,
491 plural: String,
492 n: u32,
493}
494
495impl ToString for ValueDNGetText {
496 fn to_string(&self) -> String {
497 gettextrs::dngettext(
498 self.domain.as_bytes(),
499 self.singular.as_bytes(),
500 self.plural.as_bytes(),
501 self.n,
502 )
503 }
504}
505
506#[derive(Deserialize, Clone, Debug)]
507struct ValueNPGetText {
508 ctx: String,
509 singular: String,
510 plural: String,
511 n: u32,
512}
513
514impl ToString for ValueNPGetText {
515 fn to_string(&self) -> String {
516 gettextrs::npgettext(
517 self.ctx.as_bytes(),
518 self.singular.as_bytes(),
519 self.plural.as_bytes(),
520 self.n,
521 )
522 }
523}
524
525#[derive(Deserialize, Clone, Debug)]
526struct ValueDCNGetText {
527 domain: String,
528 singular: String,
529 plural: String,
530 n: u32,
531 category: LocaleCategory,
532}
533
534#[derive(Deserialize, Debug, PartialEq, Clone, Copy)]
535#[allow(clippy::enum_variant_names)]
536enum LocaleCategory {
537 #[serde(rename = "ctype")]
538 LcCType,
539 #[serde(rename = "numeric")]
540 LcNumeric,
541 #[serde(rename = "time")]
542 LcTime,
543 #[serde(rename = "collate")]
544 LcCollate,
545 #[serde(rename = "monetary")]
546 LcMonetary,
547 #[serde(rename = "messages")]
548 LcMessages,
549 #[serde(rename = "all")]
550 LcAll,
551 #[serde(rename = "paper")]
552 LcPaper,
553 #[serde(rename = "name")]
554 LcName,
555 #[serde(rename = "address")]
556 LcAddress,
557 #[serde(rename = "telephone")]
558 LcTelephone,
559 #[serde(rename = "measurement")]
560 LcMeasurement,
561 #[serde(rename = "identification")]
562 LcIdentification,
563}
564
565impl std::convert::From<LocaleCategory> for gettextrs::LocaleCategory {
566 fn from(category: LocaleCategory) -> Self {
567 use gettextrs::LocaleCategory::*;
568
569 match category {
570 LocaleCategory::LcCType => LcCType,
571 LocaleCategory::LcNumeric => LcNumeric,
572 LocaleCategory::LcTime => LcTime,
573 LocaleCategory::LcCollate => LcCollate,
574 LocaleCategory::LcMonetary => LcMonetary,
575 LocaleCategory::LcMessages => LcMessages,
576 LocaleCategory::LcAll => LcAll,
577 LocaleCategory::LcPaper => LcPaper,
578 LocaleCategory::LcName => LcName,
579 LocaleCategory::LcAddress => LcAddress,
580 LocaleCategory::LcTelephone => LcTelephone,
581 LocaleCategory::LcMeasurement => LcMeasurement,
582 LocaleCategory::LcIdentification => LcIdentification,
583 }
584 }
585}
586
587impl ToString for ValueDCNGetText {
588 fn to_string(&self) -> String {
589 gettextrs::dcngettext(
590 self.domain.as_bytes(),
591 self.singular.as_bytes(),
592 self.plural.as_bytes(),
593 self.n,
594 self.category.into(),
595 )
596 }
597}