123 lines
3.5 KiB
Rust
123 lines
3.5 KiB
Rust
//! Store metadata — reads title.txt, description.txt, whats-new.txt, etc.
|
|
//!
|
|
//! Directory structure (mirrors Fastlane's metadata format):
|
|
//! ```text
|
|
//! store/
|
|
//! en-US/
|
|
//! title.txt
|
|
//! description.txt
|
|
//! whats-new.txt
|
|
//! keywords.txt
|
|
//! promotional-text.txt
|
|
//! de-DE/
|
|
//! title.txt
|
|
//! ...
|
|
//! ```
|
|
|
|
use crate::{PublishError, PublishResult};
|
|
use std::collections::HashMap;
|
|
use std::path::Path;
|
|
|
|
/// Metadata for a single locale.
|
|
#[derive(Debug, Clone, Default)]
|
|
pub struct LocaleMetadata {
|
|
pub title: String,
|
|
pub description: String,
|
|
pub whats_new: String,
|
|
pub keywords: Vec<String>,
|
|
pub promotional_text: String,
|
|
}
|
|
|
|
impl LocaleMetadata {
|
|
pub fn new(title: impl Into<String>, description: impl Into<String>) -> Self {
|
|
Self {
|
|
title: title.into(),
|
|
description: description.into(),
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
pub fn with_whats_new(mut self, whats_new: impl Into<String>) -> Self {
|
|
self.whats_new = whats_new.into();
|
|
self
|
|
}
|
|
}
|
|
|
|
/// Complete store metadata across all locales.
|
|
#[derive(Debug, Clone, Default)]
|
|
pub struct StoreMetadata {
|
|
pub locales: HashMap<String, LocaleMetadata>,
|
|
}
|
|
|
|
impl StoreMetadata {
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
pub fn add_locale(mut self, locale: impl Into<String>, metadata: LocaleMetadata) -> Self {
|
|
self.locales.insert(locale.into(), metadata);
|
|
self
|
|
}
|
|
|
|
/// Get metadata for a specific locale, falling back to `en-US`.
|
|
pub fn for_locale(&self, locale: &str) -> Option<&LocaleMetadata> {
|
|
self.locales.get(locale).or_else(|| self.locales.get("en-US"))
|
|
}
|
|
|
|
/// Load metadata from the directory structure.
|
|
///
|
|
/// Falls back gracefully: if the metadata directory doesn't exist,
|
|
/// returns empty metadata (don't fail the publish for missing store copy).
|
|
pub fn load_from_dir(dir: &str) -> PublishResult<Self> {
|
|
let path = Path::new(dir);
|
|
if !path.exists() {
|
|
// Warn but don't fail — metadata is optional for internal/alpha builds.
|
|
return Ok(Self::new());
|
|
}
|
|
|
|
let mut metadata = Self::new();
|
|
|
|
let read_dir = std::fs::read_dir(path).map_err(|e| {
|
|
PublishError::Metadata(format!("failed to read metadata dir {}: {}", dir, e))
|
|
})?;
|
|
|
|
for entry in read_dir.flatten() {
|
|
let locale_path = entry.path();
|
|
if !locale_path.is_dir() {
|
|
continue;
|
|
}
|
|
let locale = locale_path
|
|
.file_name()
|
|
.and_then(|n| n.to_str())
|
|
.unwrap_or("")
|
|
.to_string();
|
|
if locale.is_empty() {
|
|
continue;
|
|
}
|
|
|
|
let locale_meta = LocaleMetadata {
|
|
title: read_file(&locale_path, "title.txt"),
|
|
description: read_file(&locale_path, "description.txt"),
|
|
whats_new: read_file(&locale_path, "whats-new.txt"),
|
|
keywords: read_file(&locale_path, "keywords.txt")
|
|
.split(',')
|
|
.map(|k| k.trim().to_string())
|
|
.filter(|k| !k.is_empty())
|
|
.collect(),
|
|
promotional_text: read_file(&locale_path, "promotional-text.txt"),
|
|
};
|
|
|
|
metadata.locales.insert(locale, locale_meta);
|
|
}
|
|
|
|
Ok(metadata)
|
|
}
|
|
}
|
|
|
|
fn read_file(dir: &Path, filename: &str) -> String {
|
|
std::fs::read_to_string(dir.join(filename))
|
|
.unwrap_or_default()
|
|
.trim()
|
|
.to_string()
|
|
}
|