categories.rs 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. use crate::ui::input_category;
  2. use libc::isatty;
  3. use radix_trie::Trie;
  4. use serde::{Deserialize, Serialize};
  5. use std::cmp::Ordering;
  6. /// Category statistics for single item
  7. #[derive(Serialize, Deserialize, Debug)]
  8. pub struct CatStat {
  9. /// Category name
  10. category: String,
  11. /// How many times did the item hit this category
  12. hits: i64,
  13. }
  14. impl Ord for CatStat {
  15. fn cmp(&self, other: &Self) -> Ordering {
  16. self.hits.cmp(&other.hits)
  17. }
  18. }
  19. impl PartialOrd for CatStat {
  20. fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
  21. Some(self.cmp(other))
  22. }
  23. }
  24. impl PartialEq for CatStat {
  25. fn eq(&self, other: &Self) -> bool {
  26. self.hits == other.hits
  27. }
  28. }
  29. impl Eq for CatStat {}
  30. pub type CatStats = Trie<String, Vec<CatStat>>;
  31. /// Insert new `cat` into statistics vector or add single usage to existing cat
  32. fn update_stat(cat: &str, stat: &mut Vec<CatStat>) {
  33. let existing = stat.iter_mut().find(|stat| stat.category == cat);
  34. match existing {
  35. Some(e) => e.hits += 1,
  36. None => stat.push(CatStat {
  37. category: String::from(cat),
  38. hits: 1,
  39. }),
  40. }
  41. stat.sort_by(|a, b| b.cmp(a));
  42. }
  43. /// Set up `cat` as category for `item`: update statistics or create new item
  44. /// in `storage`
  45. pub fn assign_category(item: &str, cat: &str, storage: &mut CatStats) {
  46. if cat.is_empty() {
  47. panic!("Do not assign empty category!")
  48. }
  49. let existing = storage.get_mut(item);
  50. match existing {
  51. Some(stat) => update_stat(cat, stat),
  52. None => {
  53. let newstat = CatStat {
  54. category: String::from(cat),
  55. hits: 1,
  56. };
  57. storage.insert(String::from(item), vec![newstat]);
  58. }
  59. }
  60. }
  61. /// Return most probable category for provided `item`
  62. pub fn get_top_category<'a>(item: &str, storage: &'a CatStats) -> Option<&'a str> {
  63. match storage.get(item) {
  64. Some(stats) => Some(&stats[0].category),
  65. None => None,
  66. }
  67. }
  68. /// Choose proper category or ask user
  69. pub fn get_category(item: &str, storage: &mut CatStats) -> String {
  70. let istty = unsafe { isatty(libc::STDOUT_FILENO as i32) } != 0;
  71. if istty {
  72. let cat = input_category(item);
  73. if cat.is_empty() {
  74. match get_top_category(item, storage) {
  75. Some(cat) => String::from(cat),
  76. None => String::new(),
  77. }
  78. } else {
  79. assign_category(&item, &cat, storage);
  80. cat
  81. }
  82. } else {
  83. match get_top_category(item, storage) {
  84. Some(cat) => String::from(cat),
  85. None => String::new(),
  86. }
  87. }
  88. }
  89. #[cfg(test)]
  90. mod tests {
  91. use super::*;
  92. #[test]
  93. fn test_update_stat() {
  94. let mut stat: Vec<CatStat> = Vec::new();
  95. update_stat("test", &mut stat);
  96. assert_eq!(stat[0].hits, 1);
  97. assert_eq!(stat[0].category, "test");
  98. update_stat("test", &mut stat);
  99. assert_eq!(stat[0].hits, 2);
  100. update_stat("test2", &mut stat);
  101. assert_eq!(stat[1].category, "test2");
  102. assert_eq!(stat[1].hits, 1);
  103. update_stat("test2", &mut stat);
  104. update_stat("test2", &mut stat);
  105. assert_eq!(stat[0].category, "test2");
  106. assert_eq!(stat[0].hits, 3);
  107. assert_eq!(stat[1].hits, 2);
  108. assert_eq!(stat[1].category, "test");
  109. }
  110. #[test]
  111. fn test_assign_category() {
  112. let mut cm: Trie<String, Vec<CatStat>> = Trie::new();
  113. assign_category("item", "category", &mut cm);
  114. let stats = cm.get("item").unwrap();
  115. assert_eq!(stats[0].category, "category");
  116. assert_eq!(stats[0].hits, 1);
  117. let topcat = get_top_category("item", &mut cm).unwrap();
  118. assert_eq!(topcat, "category");
  119. }
  120. }