A library for writing host-specific, single-binary configuration management and deployment tools
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

164 lines
4.2 KiB

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<str>,
D: AsRef<str>,
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<str>,
R: CommandRunner,
{
pub fn new<D: AsRef<str>>(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<str>,
D: AsRef<str>,
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<str>,
D: AsRef<str>,
R: CommandRunner,
{
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
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<dyn Error>> {
for (source, target) in self.get_pairs() {
self
.command_runner
.run_successfully("curl", &["--compressed", "-o", &target, &source])?;
}
Ok(())
}
fn get_prerequisites(&self) -> Vec<Resource> {
vec![Resource::new("dir", self.path.as_ref())]
}
fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
Box::new(SymbolAction::new(runner, self))
}
fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
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<str>,
D: AsRef<str>,
R: CommandRunner,
{
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "WordpressTranslation {}", self.path.as_ref())
}
}