use regex::Regex; use std::cmp::max; use std::error::Error; use std::fmt; use std::fs::File as FsFile; use std::io; use std::io::{BufRead, BufReader}; use std::path::Path; use command_runner::CommandRunner; use resources::Resource; use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; pub struct WordpressTranslation<'a, C, D, R> where C: AsRef, D: AsRef, R: 'a + CommandRunner, { path: D, version: &'a str, locale: C, command_runner: &'a R, } impl<'a, C, R> WordpressTranslation<'a, C, String, R> where C: AsRef, R: CommandRunner, { pub fn new>(path: D, version: &'a str, locale: C, command_runner: &'a R) -> Self { WordpressTranslation { path: Path::new(path.as_ref()) .join("wp-content/languages") .to_string_lossy() .to_string(), version, locale, command_runner, } } } impl<'a, C, D, R> WordpressTranslation<'a, C, D, R> where C: AsRef, D: AsRef, R: CommandRunner, { fn get_pairs(&self) -> Vec<(String, String)> { let version_x = self .version .trim_end_matches(|c: char| c.is_digit(10)) .to_owned() + "x"; let locale: &str = self.locale.as_ref(); let path_locale = if locale == "de_DE" { "de".to_owned() } else { locale.to_lowercase().replace('_', "-") }; let mut res = vec![]; for &(in_slug, out_slug) in [ ("", ""), ("cc/", "continents-cities-"), ("admin/", "admin-"), ("admin/network/", "admin-network-"), ] .iter() { for format in ["po", "mo"].iter() { res.push(( format!("https://translate.wordpress.org/projects/wp/{}/{}{}/default/export-translations?format={}", version_x, in_slug, path_locale, format), format!("{}/{}{}.{}", self.path.as_ref(), out_slug, self.locale.as_ref(), format) )) } } res } } impl<'a, C, D, R> Symbol for WordpressTranslation<'a, C, D, R> where C: AsRef, D: AsRef, R: CommandRunner, { fn target_reached(&self) -> Result> { let mut newest = String::new(); let match_date = Regex::new("(?m)^\"PO-Revision-Date: (.+)\\+0000\\\\n\"$").unwrap(); for (_, target) in self.get_pairs() { match FsFile::open(target.clone()) { Err(e) => { // Check if file exists return if e.kind() == io::ErrorKind::NotFound { Ok(false) } else { Err(Box::new(e)) }; } Ok(file) => { if target.ends_with(".po") { let reader = BufReader::new(file); for content in reader.lines() { if let Some(match_result) = match_date.captures(&content?) { newest = max(newest, match_result[1].to_string()); break; } } } } } } let upstream = self.command_runner.get_output( "curl", &[&format!( "https://api.wordpress.org/core/version-check/1.7/?version={}&locale={}", self.version, self.locale.as_ref() )], )?; Ok(String::from_utf8(upstream)?.contains(&format!( r###"language":"{}","version":"{}","updated":"{}"###, self.locale.as_ref(), self.version, newest ))) } fn execute(&self) -> Result<(), Box> { for (source, target) in self.get_pairs() { self .command_runner .run_successfully("curl", &["--compressed", "-o", &target, &source])?; } Ok(()) } fn get_prerequisites(&self) -> Vec { vec![Resource::new("dir", self.path.as_ref())] } fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box { Box::new(SymbolAction::new(runner, self)) } fn into_action<'b>(self: Box, runner: &'b dyn SymbolRunner) -> Box where Self: 'b, { Box::new(OwnedSymbolAction::new(runner, *self)) } } impl<'a, C, D, R> fmt::Display for WordpressTranslation<'a, C, D, R> where C: AsRef, D: AsRef, R: CommandRunner, { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { write!(f, "WordpressTranslation {}", self.path.as_ref()) } }