diff options
Diffstat (limited to 'server')
| -rw-r--r-- | server/book-export/Cargo.toml | 15 | ||||
| -rw-r--r-- | server/book-export/src/book_html.css (renamed from server/tools/src/book_html.css) | 0 | ||||
| -rw-r--r-- | server/book-export/src/book_html.rs (renamed from server/tools/src/book_html.rs) | 0 | ||||
| -rw-r--r-- | server/book-export/src/diagram_svg.rs (renamed from server/tools/src/diagram_svg.rs) | 0 | ||||
| -rw-r--r-- | server/book-export/src/main.rs | 60 | ||||
| -rw-r--r-- | server/data/src/book/mod.rs | 6 | ||||
| -rw-r--r-- | server/locale-export/Cargo.toml | 9 | ||||
| -rw-r--r-- | server/locale-export/src/main.rs | 313 | ||||
| -rw-r--r-- | server/tools/Cargo.toml | 3 | ||||
| -rw-r--r-- | server/tools/src/diagram_dot.rs | 11 | ||||
| -rw-r--r-- | server/tools/src/main.rs | 33 |
11 files changed, 409 insertions, 41 deletions
diff --git a/server/book-export/Cargo.toml b/server/book-export/Cargo.toml new file mode 100644 index 00000000..d106dab8 --- /dev/null +++ b/server/book-export/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "hurrycurry-book-export" +version = "0.1.0" +edition = "2024" + +[dependencies] +anyhow = "1.0.100" +log = "0.4.28" +env_logger = "0.11.8" +clap = { version = "4.5.47", features = ["derive"] } +hurrycurry-protocol = { path = "../protocol" } +hurrycurry-locale = { path = "../locale" } +hurrycurry-data = { path = "../data" } +serde_json = "1.0.145" +markup = "0.15.0" diff --git a/server/tools/src/book_html.css b/server/book-export/src/book_html.css index 9171aa5e..9171aa5e 100644 --- a/server/tools/src/book_html.css +++ b/server/book-export/src/book_html.css diff --git a/server/tools/src/book_html.rs b/server/book-export/src/book_html.rs index 08a12e88..08a12e88 100644 --- a/server/tools/src/book_html.rs +++ b/server/book-export/src/book_html.rs diff --git a/server/tools/src/diagram_svg.rs b/server/book-export/src/diagram_svg.rs index d006ce78..d006ce78 100644 --- a/server/tools/src/diagram_svg.rs +++ b/server/book-export/src/diagram_svg.rs diff --git a/server/book-export/src/main.rs b/server/book-export/src/main.rs new file mode 100644 index 00000000..b57aa04f --- /dev/null +++ b/server/book-export/src/main.rs @@ -0,0 +1,60 @@ +/* + Hurry Curry! - a game about cooking + Copyright (C) 2025 Hurry Curry! Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, version 3 of the License only. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + +*/ +pub mod book_html; +pub mod diagram_svg; + +use crate::book_html::render_html_book; +use anyhow::Result; +use clap::{Parser, ValueEnum}; +use hurrycurry_data::{book::book, index::DataIndex}; +use hurrycurry_locale::FALLBACK_LOCALE; + +#[derive(Parser)] +struct Args { + #[arg(short, long)] + format: ExportFormat, + #[arg(short, long, default_value = "5star")] + map: String, +} + +#[derive(ValueEnum, Clone, Copy)] +enum ExportFormat { + Json, + Html, +} + +fn main() -> Result<()> { + env_logger::init_from_env("LOG"); + let args = Args::parse(); + + let mut index = DataIndex::default(); + index.reload()?; + let (data, serverdata) = index.generate(&args.map)?; + + let book = book(&data, &serverdata)?; + + match args.format { + ExportFormat::Json => { + println!("{}", serde_json::to_string(&book).unwrap()) + } + ExportFormat::Html => { + println!("{}", render_html_book(&data, &book, &FALLBACK_LOCALE)); + } + } + Ok(()) +} diff --git a/server/data/src/book/mod.rs b/server/data/src/book/mod.rs index f9b54d0d..263e8111 100644 --- a/server/data/src/book/mod.rs +++ b/server/data/src/book/mod.rs @@ -63,9 +63,3 @@ pub fn book(data: &Gamedata, serverdata: &Serverdata) -> Result<Book> { } Ok(Book { pages }) } - -pub fn print_book(data: &Gamedata, serverdata: &Serverdata) -> Result<()> { - let book = book(data, serverdata)?; - println!("{}", serde_json::to_string_pretty(&book).unwrap()); - Ok(()) -} diff --git a/server/locale-export/Cargo.toml b/server/locale-export/Cargo.toml new file mode 100644 index 00000000..89543937 --- /dev/null +++ b/server/locale-export/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "localetool" +version = "0.1.0" +edition = "2021" + +[dependencies] +clap = { version = "4.5.47", features = ["derive"] } +anyhow = "1.0.99" +serde_json = "1.0.145" diff --git a/server/locale-export/src/main.rs b/server/locale-export/src/main.rs new file mode 100644 index 00000000..23823716 --- /dev/null +++ b/server/locale-export/src/main.rs @@ -0,0 +1,313 @@ +use anyhow::{anyhow, Context, Result}; +use clap::Parser; +use std::{ + collections::BTreeMap, + fmt::Write as W2, + fs::{read_to_string, File}, + io::Write, + path::{Path, PathBuf}, +}; + +#[derive(Parser)] +enum Args { + ImportOldPot { + input: PathBuf, + output: PathBuf, + }, + ImportOldPo { + reference: PathBuf, + input: PathBuf, + output: PathBuf, + }, + ExportJson { + output: PathBuf, + inputs: Vec<PathBuf>, + }, + ExportGodotCsv { + input_dir: PathBuf, + output: PathBuf, + }, + ExportPo { + #[arg(long)] + remap_ids: Option<PathBuf>, + output: PathBuf, + inputs: Vec<PathBuf>, + }, +} + +static NATIVE_LANGUAGE_NAMES: &[(&str, &str)] = &[ + ("en", "English"), + ("de", "Deutsch"), + ("fr", "Français"), + ("nl", "Nederlands"), + ("es", "Español"), + ("eu", "Euskara"), + ("ja", "日本語"), + ("he", "עִברִית"), + ("tr", "Türkçe"), + ("fi", "Suomi"), + ("ar", "العربية"), + ("zh_Hans", "中文 (简化字)"), + ("zh_Hant", "中文 (繁體字)"), + ("pl", "Polski"), + ("pt", "Português"), + ("it", "Italiano"), + ("ko", "한국인"), + ("el", "ελληνικά"), + ("ru", "русский"), +]; + +fn export_load(inputs: &[PathBuf]) -> Result<BTreeMap<String, String>> { + let mut ini = BTreeMap::new(); + for path in inputs { + let f = load_ini(path)?; + for (k, v) in f { + ini.entry(k).or_insert(v); + } + } + for &(code, name) in NATIVE_LANGUAGE_NAMES { + ini.insert(format!("c.settings.ui.language.{code}"), name.to_owned()); + } + Ok(ini) +} + +fn main() -> Result<()> { + let args = Args::parse(); + match args { + Args::ExportJson { inputs, output } => { + let ini = export_load(&inputs)?; + File::create(output)?.write_all(serde_json::to_string(&ini)?.as_bytes())?; + Ok(()) + } + Args::ExportPo { + remap_ids: id_map, + output, + inputs, + } => { + let ini = export_load(&inputs)?; + let id_map = id_map.map(|path| load_ini(&path)).transpose()?; + File::create(output)?.write_all( + format!( + r#" +msgid "" +msgstr "" +"Language: {}\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +{}"#, + inputs + .first() + .ok_or(anyhow!("at least one input required"))? + .file_stem() + .ok_or(anyhow!("file name empty"))? + .to_string_lossy(), + ini.into_iter() + .try_fold(String::new(), |mut a, (mut key, value)| { + if let Some(id_map) = &id_map { + if let Some(new_id) = id_map.get(&key) { + key = new_id.to_owned() + } + } + let key = serde_json::to_string(&key)?; + let value = serde_json::to_string(&value)?; + writeln!(a, "msgid {key}\nmsgstr {value}\n\n",)?; + Ok::<_, anyhow::Error>(a) + })? + ) + .as_bytes(), + )?; + Ok(()) + } + Args::ExportGodotCsv { input_dir, output } => { + let translations = input_dir + .read_dir()? + .flat_map(|e| { + e.map_err(|e| anyhow!("{e}")) + .and_then(|e| { + if e.file_name().to_string_lossy().ends_with(".ini") { + Ok(Some(( + e.path() + .file_stem() + .ok_or(anyhow!("empty filename"))? + .to_string_lossy() + .to_string(), + load_ini(&e.path())?, + ))) + } else { + Ok(None) + } + }) + .transpose() + }) + .collect::<Result<BTreeMap<_, _>>>()?; + + let langs = translations.keys().cloned().collect::<Vec<String>>(); + let mut tr_tr = BTreeMap::<String, BTreeMap<String, String>>::new(); + for (k, v) in translations { + for (kk, vv) in v { + tr_tr.entry(kk).or_default().insert(k.clone(), vv); + } + } + + File::create(output)?.write_all( + tr_tr + .into_iter() + .try_fold(format!("id,{}\n", langs.join(",")), |mut a, (k, v)| { + writeln!( + a, + "{k},{}", + v.values() + .map(serde_json::to_string) + .collect::<Result<Vec<_>, serde_json::Error>>()? + .join(",") + )?; + Ok::<_, anyhow::Error>(a) + })? + .as_bytes(), + )?; + Ok(()) + } + Args::ImportOldPo { + reference, + input, + output, + } => { + let reference = read_to_string(reference)?; + let input = read_to_string(input)?; + + let id_reverse = reference + .lines() + .skip(1) + .map(|l| { + l.split_once("=") + .map(|(k, v)| (v, k)) + .ok_or(anyhow!("invalid ini")) + }) + .collect::<Result<BTreeMap<&str, &str>>>()?; + + let mut outmap = BTreeMap::new(); + let mut mode = 0; + let mut msgid = String::new(); + let mut msgstr = String::new(); + for (i, mut line) in input.lines().enumerate() { + if line.starts_with("#") { + continue; + } + if line.is_empty() { + continue; + } + if let Some(rest) = line.strip_prefix("msgid ") { + if !msgid.is_empty() { + if let Some(id) = id_reverse.get(&msgid.as_str()) { + outmap.insert(id.to_owned(), msgstr.clone()); + } else { + eprintln!("warning: message id {msgid:?} is unknown") + } + } + line = rest; + msgid = String::new(); + mode = 1; + } else if let Some(rest) = line.strip_prefix("msgstr ") { + line = rest; + msgstr = String::new(); + mode = 2; + } else if line.starts_with("msgctxt ") { + mode = 0; + eprintln!("warning: msgctxt not implemented (line {})", i + 1); + continue; + } + let frag = + serde_json::from_str::<String>(line).context(anyhow!("line {}", i + 1))?; + match mode { + 0 => (), + 1 => msgid.push_str(&frag), + 2 => msgstr.push_str(&frag), + _ => unreachable!(), + }; + } + + File::create(output)?.write_all( + outmap + .into_iter() + .try_fold("[hurrycurry]\n".to_string(), |mut a, (k, v)| { + writeln!(a, "{k}={v}")?; + Ok::<_, anyhow::Error>(a) + })? + .as_bytes(), + )?; + + Ok(()) + } + Args::ImportOldPot { input, output } => { + let output_raw = read_to_string(&output).unwrap_or("".to_owned()); + let input = read_to_string(input)?; + + let mut output_flip = output_raw + .lines() + .skip(1) + .map(|l| { + l.split_once("=") + .map(|(k, v)| (v.to_owned(), k.to_owned())) + .ok_or(anyhow!("invalid ini")) + }) + .collect::<Result<BTreeMap<String, String>>>()?; + + let mut id = false; + let mut msgid = String::new(); + for (i, mut line) in input.lines().enumerate() { + if line.starts_with("#") { + continue; + } + if line.is_empty() { + continue; + } + if let Some(rest) = line.strip_prefix("msgid ") { + if !msgid.is_empty() && !output_flip.contains_key(&msgid) { + output_flip.insert(msgid.replace("\n", "\\n"), format!("unknown{i}")); + } + line = rest; + id = true; + msgid = String::new(); + } else if line.starts_with("msgctxt ") || line.starts_with("msgstr ") { + id = false; + continue; + } + if id { + let frag = + serde_json::from_str::<String>(line).context(anyhow!("line {}", i + 1))?; + msgid.push_str(frag.as_str()); + } + } + + let output_unflip = output_flip + .into_iter() + .map(|(v, k)| (k, v)) + .collect::<BTreeMap<_, _>>(); + + File::create(output)?.write_all( + output_unflip + .into_iter() + .try_fold("[hurrycurry]\n".to_string(), |mut a, (k, v)| { + writeln!(a, "{k}={v}")?; + Ok::<_, anyhow::Error>(a) + })? + .as_bytes(), + )?; + + Ok(()) + } + } +} + +fn load_ini(path: &Path) -> Result<BTreeMap<String, String>> { + read_to_string(path)? + .lines() + .skip(1) + .map(|l| { + let (k, v) = l.split_once("=").ok_or(anyhow!("'=' missing"))?; + Ok::<_, anyhow::Error>((k.trim_end().to_owned(), v.trim_start().replace("%n", "\n"))) + }) + .collect() +} diff --git a/server/tools/Cargo.toml b/server/tools/Cargo.toml index 1c427c81..ffffa426 100644 --- a/server/tools/Cargo.toml +++ b/server/tools/Cargo.toml @@ -9,9 +9,6 @@ log = "0.4.28" env_logger = "0.11.8" clap = { version = "4.5.47", features = ["derive"] } hurrycurry-protocol = { path = "../protocol" } -hurrycurry-server = { path = ".." } hurrycurry-locale = { path = "../locale" } hurrycurry-data = { path = "../data" } serde_json = "1.0.145" -serde = { version = "1.0.225", features = ["derive"] } -markup = "0.15.0" diff --git a/server/tools/src/diagram_dot.rs b/server/tools/src/diagram_dot.rs index fb223dbf..1701f4ab 100644 --- a/server/tools/src/diagram_dot.rs +++ b/server/tools/src/diagram_dot.rs @@ -16,7 +16,6 @@ */ -use crate::diagram_svg::node_color; use anyhow::{Result, bail}; use hurrycurry_protocol::{ Gamedata, Message, @@ -91,6 +90,16 @@ pub fn diagram_dot(data: &Gamedata, diagram: &Diagram, use_position: bool) -> Re Ok(out) } +fn node_color(node: &DiagramNode) -> &'static str { + match node.style { + NodeStyle::FinalProduct => "#555", + NodeStyle::IntermediateProduct => "#333", + NodeStyle::ProcessActive => "#47c42b", + NodeStyle::ProcessPassive => "#c4a32b", + NodeStyle::ProcessInstant => "#5452d8", + } +} + fn node_style(attrs: &mut Vec<String>, node: &DiagramNode) { let shape = match node.style { NodeStyle::FinalProduct => "circle", diff --git a/server/tools/src/main.rs b/server/tools/src/main.rs index 8d8e4fd0..07c5d45b 100644 --- a/server/tools/src/main.rs +++ b/server/tools/src/main.rs @@ -16,17 +16,13 @@ */ -pub mod book_html; pub mod diagram_dot; -pub mod diagram_svg; pub mod graph; pub mod graph_summary; pub mod map_linter; use crate::{ - book_html::render_html_book, diagram_dot::{diagram_dot, diagram_dot_svg}, - diagram_svg::diagram_svg, graph::graph, graph_summary::graph_summary, map_linter::check_map, @@ -34,10 +30,9 @@ use crate::{ use anyhow::Result; use clap::Parser; use hurrycurry_data::{ - book::{book, diagram_layout::diagram_layout, print_book, recipe_diagram::recipe_diagram}, + book::{diagram_layout::diagram_layout, recipe_diagram::recipe_diagram}, index::DataIndex, }; -use hurrycurry_locale::FALLBACK_LOCALE; #[derive(Parser)] enum Action { @@ -46,12 +41,8 @@ enum Action { GraphSingle { out: String, #[arg(short)] - custom_svg: bool, - #[arg(short)] dot_out: bool, }, - Book, - BookHtml, MapDemands { map: String, }, @@ -73,11 +64,7 @@ fn main() -> Result<()> { match action { Action::Graph => graph()?, Action::GraphSummary => graph_summary()?, - Action::GraphSingle { - out, - custom_svg, - dot_out, - } => { + Action::GraphSingle { out, dot_out } => { let mut index = DataIndex::default(); index.reload()?; let (data, serverdata) = index.generate("5star")?; @@ -85,28 +72,12 @@ fn main() -> Result<()> { let mut diagram = recipe_diagram(&data, &serverdata, &[out])?; let out = if dot_out { diagram_dot(&data, &diagram, false)? - } else if custom_svg { - diagram_layout(&mut diagram)?; - diagram_svg(&data, &diagram)? } else { diagram_layout(&mut diagram)?; diagram_dot_svg(&data, &diagram)? }; println!("{out}"); } - Action::Book => { - let mut index = DataIndex::default(); - index.reload()?; - let (data, serverdata) = index.generate("5star")?; - print_book(&data, &serverdata)? - } - Action::BookHtml => { - let mut index = DataIndex::default(); - index.reload()?; - let (data, serverdata) = index.generate("5star")?; - let book = book(&data, &serverdata)?; - println!("{}", render_html_book(&data, &book, &FALLBACK_LOCALE)); - } Action::MapDemands { map } => { let mut index = DataIndex::default(); index.reload()?; |