diff --git a/.gitignore b/.gitignore index f5f8bf0..832b0cb 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ bin/ .vscode/ .idea/ target/ +cirup.sln \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 53ac8c3..e742b03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -233,6 +233,19 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bigdecimal" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d6867f1565b3aad85681f1015055b087fcfd840d6aeee6eee7f2da317603695" +dependencies = [ + "autocfg", + "libm", + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "bindgen" version = "0.69.5" @@ -422,7 +435,7 @@ dependencies = [ [[package]] name = "cirup_cli" -version = "0.4.0" +version = "0.5.0" dependencies = [ "cirup_core", "clap", @@ -433,7 +446,7 @@ dependencies = [ [[package]] name = "cirup_core" -version = "0.4.0" +version = "0.5.0" dependencies = [ "dot_json", "lazy_static", @@ -1660,17 +1673,36 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-conv" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + [[package]] name = "num-traits" -version = "0.2.14" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -2925,9 +2957,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "turso" -version = "0.5.0-pre.14" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7474ec0bbc596ec58e52ef9b8e3c84ce4697f7e2a081e0d7e1f32bf0da251c12" +checksum = "1326cd4c1bb82501328aadee1ca5e0f44fdb5cfe2c7cdc53d3d96aeeecece61a" dependencies = [ "mimalloc", "thiserror 2.0.18", @@ -2939,15 +2971,16 @@ dependencies = [ [[package]] name = "turso_core" -version = "0.5.0-pre.14" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a0361a896c9e2352d8c71922ba24cbc758f855fe000c102dda6f497e391cf5f" +checksum = "4befbe4e162cb691a5ff92ebcafc9028574f614cb82cf711d8ed5c89ebf23337" dependencies = [ "aegis", "aes", "aes-gcm", "antithesis_sdk", "arc-swap", + "bigdecimal", "bitflags 2.11.0", "branches", "built", @@ -2968,6 +3001,8 @@ dependencies = [ "libm", "loom", "miette", + "num-bigint", + "num-traits", "pack1", "parking_lot", "paste", @@ -2996,13 +3031,14 @@ dependencies = [ "twox-hash", "uncased", "uuid 1.21.0", + "windows-sys 0.61.2", ] [[package]] name = "turso_ext" -version = "0.5.0-pre.14" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca14f684f07a32e13edd49753e4708424e993b73009725db293e6224c9b6ff43" +checksum = "6d314de12a937acadc6a0150433d2f9837f7c49b643d7d6da49275c0713fb8d9" dependencies = [ "chrono", "getrandom 0.3.4", @@ -3011,9 +3047,9 @@ dependencies = [ [[package]] name = "turso_macros" -version = "0.5.0-pre.14" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45239eec62783a4dd945e9c4954b1fcdd04f83295845eb8c682cd5d61981bb30" +checksum = "18687fe83de76951957ce1b3599af16a4627b94c9a17fd4e09c6a28f0bbe0ca7" dependencies = [ "proc-macro2", "quote", @@ -3022,9 +3058,9 @@ dependencies = [ [[package]] name = "turso_parser" -version = "0.5.0-pre.14" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b92b5c09f736995184a6fcdc762de41dedc18f1303107c9ce16541d01183ce6" +checksum = "c41725404271e703fc734d1b211c53541821dfa2701895ec2d89c85a10e8514d" dependencies = [ "bitflags 2.11.0", "memchr", @@ -3037,9 +3073,9 @@ dependencies = [ [[package]] name = "turso_sdk_kit" -version = "0.5.0-pre.14" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adfb56d6de6b5af4130fc2c40b81805921c9028d8e0c8c04a53f2ab72be2dc4e" +checksum = "01827272c218ba5eaa2bdca87cc33e62dbdd2d9d93571a0ea260c7651e61e139" dependencies = [ "bindgen", "env_logger", @@ -3053,9 +3089,9 @@ dependencies = [ [[package]] name = "turso_sdk_kit_macros" -version = "0.5.0-pre.14" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302bd963babc219b50d7040ec958da29c8317b7f83ef5413427a476d960425e0" +checksum = "ca793575b75a8a891f0d263c1051f60ab3ed2072bdd63fe3331959bf783ad0f3" dependencies = [ "proc-macro2", "quote", @@ -3064,9 +3100,9 @@ dependencies = [ [[package]] name = "turso_sync_engine" -version = "0.5.0-pre.14" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff8485b2a3bd00ad1da85eca71fa80df39a8addd0ff45bc167732808e041e5a7" +checksum = "2e040a526375f699f162dc75a64d21b43e193c3dc3f22fcb34e190db8f8ba39a" dependencies = [ "base64 0.22.1", "bytes", @@ -3086,9 +3122,9 @@ dependencies = [ [[package]] name = "turso_sync_sdk_kit" -version = "0.5.0-pre.14" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a282a0ee439c027d214874c7723ffc9e0994992af9fdb92ff9bc932029015d5" +checksum = "547f4853402531c949b5c2d343b87b1b7ea709651695ccd207e6072504486cce" dependencies = [ "bindgen", "env_logger", diff --git a/README.md b/README.md index 1d7d8e5..60aa56d 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,16 @@ -# cirup-rs +# cirup `cirup` is a command-line tool for working with localization resource files. It supports `.json`, `.resx`, and `.restext` files. ## Quick start +Install `cirup` globally as a .NET tool: + +```bash +dotnet tool install -g Devolutions.Cirup.Tool +``` + Build the workspace: ```bash @@ -28,6 +34,7 @@ cirup --help - `-v`, `-vv`, ...: increase log verbosity. - `-C`, `--show-changes`: for `file-diff`, include keys that exist in both files but have different values. - `--touch`: force writing output files even when generated bytes are identical. +- `--output-encoding `: control output file encoding. `utf8` behaves like `utf8-no-bom`. By default, cirup avoids rewriting output files when content has not changed. diff --git a/cirup_cli/Cargo.toml b/cirup_cli/Cargo.toml index 7ec50b4..d18436b 100644 --- a/cirup_cli/Cargo.toml +++ b/cirup_cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cirup_cli" -version = "0.4.0" +version = "0.5.0" authors = ["Marc-André Moreau "] edition = "2024" diff --git a/cirup_cli/src/main.rs b/cirup_cli/src/main.rs index bb2d959..29a1a1e 100644 --- a/cirup_cli/src/main.rs +++ b/cirup_cli/src/main.rs @@ -1,11 +1,30 @@ use std::error::Error; use std::process::ExitCode; -use clap::{ArgAction, Parser, Subcommand}; +use clap::{ArgAction, Parser, Subcommand, ValueEnum}; use env_logger::{Builder, Env}; use log::error; -use cirup_core::query; +use cirup_core::{OutputEncoding, query}; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueEnum)] +enum CliOutputEncoding { + #[value(name = "utf8-no-bom")] + Utf8NoBom, + #[value(name = "utf8-bom")] + Utf8Bom, + #[value(name = "utf8")] + Utf8, +} + +impl From for OutputEncoding { + fn from(value: CliOutputEncoding) -> Self { + match value { + CliOutputEncoding::Utf8NoBom | CliOutputEncoding::Utf8 => OutputEncoding::Utf8NoBom, + CliOutputEncoding::Utf8Bom => OutputEncoding::Utf8Bom, + } + } +} #[derive(Debug, Parser)] #[command(name = "cirup", author, version, about = "a translation continuous integration tool")] @@ -19,6 +38,15 @@ struct Cli { #[arg(long = "touch", global = true, action = ArgAction::SetTrue, help = "force writing output files even when output content has not changed")] touch: bool, + #[arg( + long = "output-encoding", + global = true, + value_enum, + default_value = "utf8-no-bom", + help = "output file encoding: utf8-no-bom (default), utf8-bom, utf8" + )] + output_encoding: CliOutputEncoding, + #[command(subcommand)] command: Commands, } @@ -84,48 +112,48 @@ enum Commands { DiffWithBase { old: String, new: String, base: String }, } -fn print(input: &str, out_file: Option<&str>, touch: bool) { +fn print(input: &str, out_file: Option<&str>, touch: bool, output_encoding: OutputEncoding) { let query = query::query_print(input); - query.run_interactive(out_file, touch); + query.run_interactive_with_encoding(out_file, touch, output_encoding); } -fn diff(file_one: &str, file_two: &str, out_file: Option<&str>, touch: bool) { +fn diff(file_one: &str, file_two: &str, out_file: Option<&str>, touch: bool, output_encoding: OutputEncoding) { let query = query::query_diff(file_one, file_two); - query.run_interactive(out_file, touch); + query.run_interactive_with_encoding(out_file, touch, output_encoding); } -fn change(file_one: &str, file_two: &str, out_file: Option<&str>, touch: bool) { +fn change(file_one: &str, file_two: &str, out_file: Option<&str>, touch: bool, output_encoding: OutputEncoding) { let query = query::query_change(file_one, file_two); - query.run_interactive(out_file, touch); + query.run_interactive_with_encoding(out_file, touch, output_encoding); } -fn merge(file_one: &str, file_two: &str, out_file: Option<&str>, touch: bool) { +fn merge(file_one: &str, file_two: &str, out_file: Option<&str>, touch: bool, output_encoding: OutputEncoding) { let query = query::query_merge(file_one, file_two); - query.run_interactive(out_file, touch); + query.run_interactive_with_encoding(out_file, touch, output_encoding); } -fn intersect(file_one: &str, file_two: &str, out_file: Option<&str>, touch: bool) { +fn intersect(file_one: &str, file_two: &str, out_file: Option<&str>, touch: bool, output_encoding: OutputEncoding) { let query = query::query_intersect(file_one, file_two); - query.run_interactive(out_file, touch); + query.run_interactive_with_encoding(out_file, touch, output_encoding); } -fn subtract(file_one: &str, file_two: &str, out_file: Option<&str>, touch: bool) { +fn subtract(file_one: &str, file_two: &str, out_file: Option<&str>, touch: bool, output_encoding: OutputEncoding) { let query = query::query_subtract(file_one, file_two); - query.run_interactive(out_file, touch); + query.run_interactive_with_encoding(out_file, touch, output_encoding); } -fn convert(file_one: &str, out_file: &str, touch: bool) { +fn convert(file_one: &str, out_file: &str, touch: bool, output_encoding: OutputEncoding) { let query = query::query_convert(file_one); - query.run_interactive(Some(out_file), touch); + query.run_interactive_with_encoding(Some(out_file), touch, output_encoding); } -fn sort(file_one: &str, out_file: Option<&str>, touch: bool) { +fn sort(file_one: &str, out_file: Option<&str>, touch: bool, output_encoding: OutputEncoding) { let query = query::query_sort(file_one); if out_file.is_some() { - query.run_interactive(out_file, touch); + query.run_interactive_with_encoding(out_file, touch, output_encoding); } else { - query.run_interactive(Some(file_one), touch); + query.run_interactive_with_encoding(Some(file_one), touch, output_encoding); } } @@ -135,37 +163,39 @@ fn diff_with_base(old: &str, new: &str, base: &str) { } fn run(cli: &Cli) -> Result<(), Box> { + let output_encoding: OutputEncoding = cli.output_encoding.into(); + match &cli.command { Commands::FilePrint { file, output } => { - print(file, output.as_deref(), cli.touch); + print(file, output.as_deref(), cli.touch, output_encoding); Ok(()) } Commands::FileDiff { file1, file2, output } => { if cli.show_changes { - change(file1, file2, output.as_deref(), cli.touch); + change(file1, file2, output.as_deref(), cli.touch, output_encoding); } else { - diff(file1, file2, output.as_deref(), cli.touch); + diff(file1, file2, output.as_deref(), cli.touch, output_encoding); } Ok(()) } Commands::FileMerge { file1, file2, output } => { - merge(file1, file2, output.as_deref(), cli.touch); + merge(file1, file2, output.as_deref(), cli.touch, output_encoding); Ok(()) } Commands::FileIntersect { file1, file2, output } => { - intersect(file1, file2, output.as_deref(), cli.touch); + intersect(file1, file2, output.as_deref(), cli.touch, output_encoding); Ok(()) } Commands::FileSubtract { file1, file2, output } => { - subtract(file1, file2, output.as_deref(), cli.touch); + subtract(file1, file2, output.as_deref(), cli.touch, output_encoding); Ok(()) } Commands::FileConvert { file, output } => { - convert(file, output, cli.touch); + convert(file, output, cli.touch, output_encoding); Ok(()) } Commands::FileSort { file, output } => { - sort(file, output.as_deref(), cli.touch); + sort(file, output.as_deref(), cli.touch, output_encoding); Ok(()) } Commands::DiffWithBase { old, new, base } => { @@ -235,6 +265,7 @@ mod tests { let cli = Cli::parse_from(["cirup", "--touch", "file-sort", "a.json"]); assert!(cli.touch); + assert_eq!(cli.output_encoding, CliOutputEncoding::Utf8NoBom); match cli.command { Commands::FileSort { file, output } => { assert_eq!(file, "a.json"); @@ -243,4 +274,27 @@ mod tests { _ => panic!("expected file-sort command"), } } + + #[test] + fn parse_output_encoding_values() { + let bom = Cli::parse_from([ + "cirup", + "--output-encoding", + "utf8-bom", + "file-convert", + "a.json", + "b.restext", + ]); + assert_eq!(bom.output_encoding, CliOutputEncoding::Utf8Bom); + + let utf8 = Cli::parse_from([ + "cirup", + "--output-encoding", + "utf8", + "file-convert", + "a.json", + "b.restext", + ]); + assert_eq!(utf8.output_encoding, CliOutputEncoding::Utf8); + } } diff --git a/cirup_core/Cargo.toml b/cirup_core/Cargo.toml index 30e020b..0dca18b 100644 --- a/cirup_core/Cargo.toml +++ b/cirup_core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cirup_core" -version = "0.4.0" +version = "0.5.0" authors = ["Marc-André Moreau "] edition = "2024" @@ -38,7 +38,7 @@ features = ["rt"] optional = true [dependencies.turso] -version = "0.5.0-pre.14" +version = "0.5.0" optional = true [dependencies.libsql] diff --git a/cirup_core/src/file.rs b/cirup_core/src/file.rs index cd9afe3..93487de 100644 --- a/cirup_core/src/file.rs +++ b/cirup_core/src/file.rs @@ -1,6 +1,5 @@ use std::fs; use std::io::Read; -use std::io::prelude::*; use std::path::Path; use std::collections::HashMap; @@ -16,6 +15,13 @@ use std::error::Error; const UTF8_BOM: [u8; 3] = [0xEF, 0xBB, 0xBF]; +#[derive(Debug, PartialEq, Eq, Copy, Clone, Default)] +pub enum OutputEncoding { + #[default] + Utf8NoBom, + Utf8Bom, +} + #[derive(Debug, PartialEq, Eq, Copy, Clone)] pub(crate) enum FormatType { Unknown, @@ -29,7 +35,6 @@ pub(crate) trait FileFormat { fn parse_from_str(&self, text: &str) -> Result, Box>; fn parse_from_file(&self, filename: &str) -> Result, Box>; fn write_to_str(&self, resources: &[Resource]) -> String; - fn write_to_file(&self, filename: &str, resources: &[Resource]); } pub(crate) fn get_format_type_from_extension(extension: &str) -> FormatType { @@ -51,11 +56,6 @@ pub(crate) fn load_string_from_file(filename: &str) -> Result [u8; 32] { let mut hasher = Sha256::new(); hasher.update(bytes); @@ -74,23 +74,38 @@ fn should_write_output(output_hash: [u8; 32], existing_bytes: Option<&[u8]>, tou output_hash != sha256_hash(existing_bytes) } -fn output_bytes_for_format(format_type: FormatType, resources: &[Resource]) -> Vec { +fn encode_utf8(text: &str, output_encoding: OutputEncoding) -> Vec { + match output_encoding { + OutputEncoding::Utf8NoBom => text.as_bytes().to_vec(), + OutputEncoding::Utf8Bom => { + let mut output = Vec::with_capacity(UTF8_BOM.len() + text.len()); + output.extend_from_slice(&UTF8_BOM); + output.extend_from_slice(text.as_bytes()); + output + } + } +} + +fn output_bytes_for_format( + format_type: FormatType, + resources: &[Resource], + output_encoding: OutputEncoding, +) -> Vec { match format_type { FormatType::Json => { let file_format = JsonFileFormat {}; - file_format.write_to_str(resources).into_bytes() + let text = file_format.write_to_str(resources); + encode_utf8(&text, output_encoding) } FormatType::Resx => { let file_format = ResxFileFormat {}; - file_format.write_to_str(resources).into_bytes() + let text = file_format.write_to_str(resources); + encode_utf8(&text, output_encoding) } FormatType::Restext => { let file_format = RestextFileFormat {}; let text = file_format.write_to_str(resources); - let mut output = Vec::with_capacity(UTF8_BOM.len() + text.len()); - output.extend_from_slice(&UTF8_BOM); - output.extend_from_slice(text.as_bytes()); - output + encode_utf8(&text, output_encoding) } FormatType::Unknown => Vec::new(), } @@ -139,6 +154,15 @@ pub(crate) fn load_resource_file(filename: &str) -> Result, Box { - let file_format = JsonFileFormat {}; - file_format.write_to_file(filename, resources) - } - FormatType::Resx => { - let file_format = ResxFileFormat {}; - file_format.write_to_file(filename, resources) - } - FormatType::Restext => { - let file_format = RestextFileFormat {}; - file_format.write_to_file(filename, resources) - } - FormatType::Unknown => {} - } + fs::write(filename, output_bytes).expect("failed to write output file"); } } @@ -247,9 +257,16 @@ fn should_write_when_touch_is_true_even_if_hashes_match() { } #[test] -fn restext_output_bytes_include_utf8_bom() { +fn restext_output_bytes_do_not_include_utf8_bom() { + let resources = vec![Resource::new("hello", "world")]; + let output = output_bytes_for_format(FormatType::Restext, &resources, OutputEncoding::Utf8NoBom); + assert!(!output.starts_with(&UTF8_BOM)); +} + +#[test] +fn restext_output_bytes_include_utf8_bom_when_configured() { let resources = vec![Resource::new("hello", "world")]; - let output = output_bytes_for_format(FormatType::Restext, &resources); + let output = output_bytes_for_format(FormatType::Restext, &resources, OutputEncoding::Utf8Bom); assert!(output.starts_with(&UTF8_BOM)); } diff --git a/cirup_core/src/json.rs b/cirup_core/src/json.rs index 8c47e67..e65f75c 100644 --- a/cirup_core/src/json.rs +++ b/cirup_core/src/json.rs @@ -8,7 +8,7 @@ use serde_json::{Map, Value}; use crate::Resource; use crate::file::FileFormat; -use crate::file::{load_string_from_file, save_string_to_file}; +use crate::file::load_string_from_file; use std::error::Error; pub(crate) struct JsonFileFormat {} @@ -75,11 +75,6 @@ impl FileFormat for JsonFileFormat { json_to_string_pretty(&root_map) } - - fn write_to_file(&self, filename: &str, resources: &[Resource]) { - let text = self.write_to_str(resources); - save_string_to_file(filename, text.as_str()); - } } #[test] diff --git a/cirup_core/src/lib.rs b/cirup_core/src/lib.rs index d902d1d..ffb78e4 100644 --- a/cirup_core/src/lib.rs +++ b/cirup_core/src/lib.rs @@ -29,6 +29,7 @@ mod restext; mod resx; mod file; +pub use crate::file::OutputEncoding; mod query_backend; pub mod query; diff --git a/cirup_core/src/query.rs b/cirup_core/src/query.rs index 4283fa4..88fb7ed 100644 --- a/cirup_core/src/query.rs +++ b/cirup_core/src/query.rs @@ -3,7 +3,7 @@ use prettytable::{Cell, Row, Table}; use crate::config::{QueryBackendKind, QueryConfig}; -use crate::file::save_resource_file; +use crate::file::{OutputEncoding, save_resource_file, save_resource_file_with_encoding}; use crate::query_backend::{QueryBackend, build_backend}; use crate::{Resource, Triple}; @@ -296,6 +296,16 @@ impl CirupQuery { } } + pub fn run_interactive_with_encoding(&self, out_file: Option<&str>, touch: bool, output_encoding: OutputEncoding) { + let resources = self.run(); + + if let Some(out_file) = out_file { + save_resource_file_with_encoding(out_file, &resources, touch, output_encoding); + } else { + print_resources_pretty(&resources); + } + } + pub fn run_triple_interactive(&self) { let triples = self.run_triple(); print_triples_pretty(&triples); diff --git a/cirup_core/src/restext.rs b/cirup_core/src/restext.rs index c5fcbc7..611ebcb 100644 --- a/cirup_core/src/restext.rs +++ b/cirup_core/src/restext.rs @@ -1,7 +1,5 @@ use regex::Regex; use std::fmt; -use std::fs; -use std::io::prelude::*; use crate::Resource; use crate::file::FileFormat; @@ -41,6 +39,7 @@ impl FileFormat for RestextFileFormat { fn parse_from_str(&self, text: &str) -> Result, Box> { let mut resources: Vec = Vec::new(); + let text = text.strip_prefix('\u{feff}').unwrap_or(text); for line in text.lines() { if REGEX_RESTEXT.is_match(line) { @@ -75,15 +74,6 @@ impl FileFormat for RestextFileFormat { output } - - fn write_to_file(&self, filename: &str, resources: &[Resource]) { - let bom: [u8; 3] = [0xEF, 0xBB, 0xBF]; - let text = self.write_to_str(resources); - let mut file = fs::File::create(filename).expect("failed to create restext file"); - file.write_all(&bom).expect("failed to write UTF-8 BOM"); - file.write_all(text.as_bytes()) - .expect("failed to write restext content"); - } } #[test] @@ -112,6 +102,22 @@ fn test_restext_parse() { assert_eq!(resource.value, "Who let the dogs out?"); } +#[test] +fn test_restext_parse_with_utf8_bom() { + let text = "\u{feff}lblBoat=I'm on a boat.\r\n"; + + let file_format = RestextFileFormat {}; + + let resources = match file_format.parse_from_str(text) { + Ok(resources) => resources, + Err(e) => panic!("restext parse with bom failed: {}", e), + }; + + let resource = &resources[0]; + assert_eq!(resource.name, "lblBoat"); + assert_eq!(resource.value, "I'm on a boat."); +} + #[test] fn test_restext_write() { let file_format = RestextFileFormat {}; diff --git a/cirup_core/src/resx.rs b/cirup_core/src/resx.rs index 5d48ef7..05867a1 100644 --- a/cirup_core/src/resx.rs +++ b/cirup_core/src/resx.rs @@ -3,7 +3,7 @@ use treexml::{Document, Element}; use crate::Resource; use crate::file::FileFormat; -use crate::file::{load_string_from_file, save_string_to_file}; +use crate::file::load_string_from_file; use std::error::Error; pub(crate) struct ResxFileFormat {} @@ -70,11 +70,6 @@ impl FileFormat for ResxFileFormat { output.push_str("\n"); output } - - fn write_to_file(&self, filename: &str, resources: &[Resource]) { - let text = self.write_to_str(resources); - save_string_to_file(filename, text.as_str()); - } } #[test] diff --git a/nuget/Devolutions.Cirup.Build.Package.csproj b/nuget/Devolutions.Cirup.Build.Package.csproj index 6f44cd3..5292c25 100644 --- a/nuget/Devolutions.Cirup.Build.Package.csproj +++ b/nuget/Devolutions.Cirup.Build.Package.csproj @@ -2,12 +2,13 @@ net8.0 true - true + false + true false true Devolutions.Cirup.Build - 0.0.0-local + 0.5.0 Devolutions Cross-platform cirup executable packaged for MSBuild pre-build RESX sorting. README.md diff --git a/nuget/samples/Devolutions.Cirup.Build.E2E/Devolutions.Cirup.Build.E2E.csproj b/nuget/samples/Devolutions.Cirup.Build.E2E/Devolutions.Cirup.Build.E2E.csproj index 20b4bc0..b317180 100644 --- a/nuget/samples/Devolutions.Cirup.Build.E2E/Devolutions.Cirup.Build.E2E.csproj +++ b/nuget/samples/Devolutions.Cirup.Build.E2E/Devolutions.Cirup.Build.E2E.csproj @@ -5,7 +5,7 @@ enable enable false - 0.0.0-local + 0.5.0 diff --git a/nuget/test-e2e.ps1 b/nuget/test-e2e.ps1 index 443bdfb..480d6ad 100644 --- a/nuget/test-e2e.ps1 +++ b/nuget/test-e2e.ps1 @@ -1,5 +1,5 @@ param( - [string]$Version = "0.0.0-local", + [string]$Version = "0.5.0", [string]$Configuration = "Release" ) diff --git a/nuget/tool/Devolutions.Cirup.Tool.csproj b/nuget/tool/Devolutions.Cirup.Tool.csproj index 0e28b12..eb74f98 100644 --- a/nuget/tool/Devolutions.Cirup.Tool.csproj +++ b/nuget/tool/Devolutions.Cirup.Tool.csproj @@ -7,7 +7,7 @@ cirup Devolutions.Cirup.Tool - 0.0.0-local + 0.5.0 Devolutions RID-specific dotnet tool wrapper around prebuilt cirup native executables. README.md