us_excel_template_data/
catalog.rs1const RAW: &str = include_str!("../data/excel-template-search-demand-us-2026.csv");
8
9#[derive(Debug, Clone, PartialEq)]
11pub struct Row {
12 pub keyword: &'static str,
14 pub monthly_search_volume_us: u32,
16 pub category: &'static str,
18 pub free_template_url: Option<&'static str>,
21}
22
23#[derive(Debug, Clone, PartialEq)]
25pub struct CategoryStats {
26 pub category: &'static str,
28 pub keywords: usize,
30 pub combined_monthly_volume_us: u64,
32}
33
34pub fn all() -> Vec<Row> {
36 RAW.lines()
37 .skip(1)
38 .filter(|l| !l.trim().is_empty())
39 .map(|line| {
40 let mut it = line.splitn(5, ',');
41 let keyword = it.next().unwrap_or_default();
42 let volume = it.next().unwrap_or("0").parse().unwrap_or(0);
43 let category = it.next().unwrap_or_default();
44 let _has_free = it.next();
45 let url = it.next().unwrap_or_default().trim();
46 Row {
47 keyword,
48 monthly_search_volume_us: volume,
49 category,
50 free_template_url: if url.is_empty() { None } else { Some(url) },
51 }
52 })
53 .collect()
54}
55
56pub fn top(n: usize) -> Vec<Row> {
58 let mut rows = all();
59 rows.sort_by(|a, b| b.monthly_search_volume_us.cmp(&a.monthly_search_volume_us));
60 rows.truncate(n);
61 rows
62}
63
64pub fn search(needle: &str) -> Vec<Row> {
66 let q = needle.to_lowercase();
67 let mut rows: Vec<Row> = all()
68 .into_iter()
69 .filter(|r| r.keyword.to_lowercase().contains(&q))
70 .collect();
71 rows.sort_by(|a, b| b.monthly_search_volume_us.cmp(&a.monthly_search_volume_us));
72 rows
73}
74
75pub fn by_category(slug: &str) -> Vec<Row> {
77 let mut rows: Vec<Row> = all().into_iter().filter(|r| r.category == slug).collect();
78 rows.sort_by(|a, b| b.monthly_search_volume_us.cmp(&a.monthly_search_volume_us));
79 rows
80}
81
82pub fn categories() -> Vec<CategoryStats> {
84 let mut cats: Vec<CategoryStats> = Vec::new();
85 for row in all() {
86 match cats.iter_mut().find(|c| c.category == row.category) {
87 Some(c) => {
88 c.keywords += 1;
89 c.combined_monthly_volume_us += u64::from(row.monthly_search_volume_us);
90 }
91 None => cats.push(CategoryStats {
92 category: row.category,
93 keywords: 1,
94 combined_monthly_volume_us: u64::from(row.monthly_search_volume_us),
95 }),
96 }
97 }
98 cats.sort_by(|a, b| b.combined_monthly_volume_us.cmp(&a.combined_monthly_volume_us));
99 cats
100}
101
102pub fn with_free_template() -> Vec<Row> {
104 let mut rows: Vec<Row> = all().into_iter().filter(|r| r.free_template_url.is_some()).collect();
105 rows.sort_by(|a, b| b.monthly_search_volume_us.cmp(&a.monthly_search_volume_us));
106 rows
107}