diff --git a/dsc/Cargo.lock b/dsc/Cargo.lock index 45a56a51..1e6d65e3 100644 --- a/dsc/Cargo.lock +++ b/dsc/Cargo.lock @@ -110,6 +110,12 @@ version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + [[package]] name = "arrayvec" version = "0.7.6" @@ -137,6 +143,12 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "base62" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fa474cf7492f9a299ba6019fb99ec673e1739556d48e8a90eabaea282ef0e4" + [[package]] name = "base64" version = "0.22.1" @@ -200,6 +212,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3eeab4423108c5d7c744f4d234de88d18d636100093ae04caf4825134b9c3a32" +[[package]] +name = "bstr" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -494,6 +516,7 @@ dependencies = [ "indicatif", "jsonschema 0.26.1", "path-absolutize", + "rust-i18n", "schemars", "serde", "serde_json", @@ -570,12 +593,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -610,6 +633,12 @@ dependencies = [ "regex-syntax 0.8.5", ] +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + [[package]] name = "flate2" version = "1.0.34" @@ -673,6 +702,36 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "globset" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", +] + +[[package]] +name = "globwalk" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" +dependencies = [ + "bitflags 1.3.2", + "ignore", + "walkdir", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -866,6 +925,22 @@ dependencies = [ "utf8_iter", ] +[[package]] +name = "ignore" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata 0.4.8", + "same-file", + "walkdir", + "winapi-util", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -925,6 +1000,15 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -1002,6 +1086,12 @@ version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" +[[package]] +name = "libyml" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64804cc6a5042d4f05379909ba25b503ec04e2c082151d62122d5dcaa274b961" + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -1085,6 +1175,15 @@ dependencies = [ "libc", ] +[[package]] +name = "normpath" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8911957c4b1549ac0dc74e30db9c8b0e66ddcd6d7acc33098f4c63a64a6d7ed" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "ntapi" version = "0.4.1" @@ -1446,6 +1545,60 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "rust-i18n" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "039f57d22229db401af3458ca939300178e99e88b938573cea12b7c2b0f09724" +dependencies = [ + "globwalk", + "once_cell", + "regex", + "rust-i18n-macro", + "rust-i18n-support", + "smallvec", +] + +[[package]] +name = "rust-i18n-macro" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde5c022360a2e54477882843d56b6f9bcb4bc62f504b651a2f497f0028d174f" +dependencies = [ + "glob", + "once_cell", + "proc-macro2", + "quote", + "rust-i18n-support", + "serde", + "serde_json", + "serde_yml", + "syn", +] + +[[package]] +name = "rust-i18n-support" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75d2844d36f62b5d6b66f9cf8f8cbdbbbdcdb5fd37a473a9cc2fb45fdcf485d2" +dependencies = [ + "arc-swap", + "base62", + "globwalk", + "itertools", + "lazy_static", + "normpath", + "once_cell", + "proc-macro2", + "regex", + "serde", + "serde_json", + "serde_yml", + "siphasher", + "toml", + "triomphe", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -1454,15 +1607,15 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" -version = "0.38.37" +version = "0.38.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" dependencies = [ "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1569,6 +1722,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + [[package]] name = "serde_yaml" version = "0.9.34+deprecated" @@ -1582,6 +1744,23 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "serde_yml" +version = "0.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e76bab63c3fd98d27c17f9cbce177f64a91f5e69ac04cafe04e1bb25d1dc3c" +dependencies = [ + "indexmap 2.6.0", + "itoa", + "libyml", + "log", + "memchr", + "ryu", + "serde", + "serde_json", + "tempfile", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -1627,6 +1806,12 @@ dependencies = [ "libc", ] +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "smallvec" version = "1.13.2" @@ -1719,6 +1904,19 @@ dependencies = [ "windows", ] +[[package]] +name = "tempfile" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "thiserror" version = "1.0.64" @@ -1854,6 +2052,40 @@ dependencies = [ "syn", ] +[[package]] +name = "toml" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.6.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tracing" version = "0.1.40" @@ -1979,6 +2211,17 @@ dependencies = [ "tree-sitter-language", ] +[[package]] +name = "triomphe" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8f7726da4807b58ea5c96fdc122f80702030edc33b35aff9190a51148ccc85" +dependencies = [ + "arc-swap", + "serde", + "stable_deref_trait", +] + [[package]] name = "unicode-bidi" version = "0.3.17" @@ -2355,6 +2598,15 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + [[package]] name = "write16" version = "1.0.0" diff --git a/dsc/Cargo.toml b/dsc/Cargo.toml index 97c53e9b..6ce3b122 100644 --- a/dsc/Cargo.toml +++ b/dsc/Cargo.toml @@ -19,6 +19,7 @@ dsc_lib = { path = "../dsc_lib" } indicatif = { version = "0.17" } jsonschema = { version = "0.26", default-features = false } path-absolutize = { version = "3.1" } +rust-i18n = { version = "3" } schemars = { version = "0.8" } serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", features = ["preserve_order"] } diff --git a/dsc/locales/en-us.toml b/dsc/locales/en-us.toml new file mode 100644 index 00000000..5000b20c --- /dev/null +++ b/dsc/locales/en-us.toml @@ -0,0 +1,128 @@ +_version = 1 + +[args] +about = "Apply configuration or invoke specific DSC resources" +traceFormat = "Trace format to use" +traceLevel = "Trace level to use" +completer = "Generate a shell completion script" +configAbout = "Apply a configuration document" +parameters = "Parameters to pass to the configuration as JSON or YAML" +parametersFile = "Parameters to pass to the configuration as a JSON or YAML file" +systemRoot = "Specify the operating system root path if not targeting the current running OS" +resourceAbout = "Invoke a specific DSC resource" +schemaAbout = "Get the JSON schema for a DSC type" +schemaType = "The type of DSC schema to get" +outputFormat = "The output format to use" +input = "The input document as JSON or YAML to pass to the configuration or resource" +file = "The path to a file used as input to the configuration or resource. Use '-' for the file to read from STDIN." +whatIf = "Run as a what-if operation instead of executing the configuration or resource" +getAbout = "Retrieve the current configuration" +setAbout = "Set the current configuration" +testAbout = "Test the current configuration" +validateAbout = "Validate the current configuration" +exportAbout = "Export the current configuration" +resolveAbout = "Resolve the current configuration" +listAbout = "List or find resources" +adapter = "Adapter filter to limit the resource search" +description = "Description keyword to search for in the resource description" +tags = "Tag to search for in the resource tags" +resourceGet = "Invoke the get operation to a resource" +getAll = "Get all instances of the resource" +resource = "The name of the resource to invoke" + +[main] +ctrlCReceived = "Ctrl-C received" +failedCtrlCHandler = "Failed to set Ctrl-C handler" +failedReadingParametersFile = "Failed to read parameters file" +generatingCompleter = "Generating completion script for" +readingParametersFile = "Reading parameters from file" +usingDscVersion = "Running DSC version" +foundProcesses = "Found processes" +failedToGetPid = "Could not get current process id" +currentPid = "Current process id" +failedToGetProcess = "Could not get current process" +terminatingSubprocess ="Terminating subprocesses of process" +terminatingProcess = "Terminating process" +failedTerminatingProcess = "Failed to terminate process" +storeMessage = """DSC.exe is a command-line tool and cannot be run directly from the Windows Store or Explorer. +Visit https://aka.ms/dscv3-docs for more information on how to use DSC.exe. + +Press any key to close this window""" + +[resolve] +processingInclude = "Processing Include input" +invalidInclude = "Failed to deserialize Include input" +failedToReadFile = "Failed to read file" +failedToOpenFile = "Failed to open included file" +invalidFileContent = "Invalid UTF-8 sequence in included file" +invalidFile = "Failed to read the configuration file as YAML or JSON" +resolvingParameters = "Resolving parameters from file" +failedParseParametersFile = "Failed to parse parameters file to JSON" +failedResolveParametersFile = "Failed to resolve parameters file" +noParametersFile = "No parameters file found" +invalidPath = "Include path must not contain '..'" +failedGetCurrentDirectory = "Failed to get current directory" + +[resource_command] +implementedAs = "implemented as" +invalidOperationOnAdapter = "Can not perform this operation on the adapter itself" +adapterNotFound = "Adapter not found" +setInputEmpty = "Desired input is empty" +testInputEmpty = "Expected input is required" + +[subcommand] +actualStateNotObject = "actual_state is not an object" +unexpectedTestResult = "Unexpected Group TestResult" +message = "message" +currentDirectory = "current directory" +noParameters = "No parameters specified" +parameters = "Parameters specified" +failedConvertJson = "Failed to convert YAML to JSON" +invalidParamters = "Parameters are not valid JSON or YAML" +invalidPath = "Target path does not exist" +failedSetParameters = "Parameter input failure" +invalidInclude = "Failed to deserialize Include input" +failedSerialize = "Failed to convert validation result to JSON" +invalidConfiguration = "Failed to deserialize configuration" +failedSerializeResolve = "Failed to serialize resolve result" +validatingConfiguration = "Validating configuration against schema" +noResources = "Resources not specified" +resourceTypeNotSpecified = "Resource type not specified" +validatingResource = "Validating resource named" +resourceNotFound = "Resource type not found" +resourceImplementsValidate = "Resource implements validation" +noReason = "No reason provided" +resourceValidationFailed = "Resource failed validation" +resourceDoesNotImplementValidate = "Resource does not implement validation, using schema" +noSchemaOrValidate = "Resource does not have a schema nor supports validation" +noManifest = "Resource does not have a manifest" +tableHeader_type = "Type" +tableHeader_kind = "Kind" +tableHeader_version = "Version" +tableheader_capabilities = "Capabilities" +tableHeader_adapter = "RequireAdapter" +tableHeader_description = "Description" +invalidManifest = "Error in manifest for" + +[util] +failedToConvertJsonToString = "Failed to convert JSON to string" +failedToReadTracingSetting = "Could not read 'tracing' setting" +invalidTraceLevel = "Default to 'warn', invalid DSC_TRACE_LEVEL value" +failedToSetTracing = "Unable to set global default tracing subscriber. Tracing is diabled." +validatingSchema = "Validating against schema" +failedToCompileSchema = "JSON Schema Compilation" +validationFailed = "Failed validation" +readingInput = "Reading input from command line parameter" +inputIsFile = "Document provided is a file path, use '--file' instead" +readingInputFromFile = "Reading input from file" +readingInputFromStdin = "Reading input from STDIN" +invalidUtf8 = "Invalid utf-8 input" +failedToReadStdin = "Failed to read input from STDIN" +failedToReadFile = "Failed to read input file" +noInput = "No input provided" +emptyInput = "Empty input provided" +failedToParseInput = "Invalid JSON or YAML" +failedToAbsolutizePath = "Error making config path absolute" +failedToGetParentPath = "Error reading config path parent" +dscConfigRootAlreadySet = "The current value of DSC_CONFIG_ROOT env var will be overridden" +settingDscConfigRoot = "Setting DSC_CONFIG_ROOT env var as" diff --git a/dsc/src/args.rs b/dsc/src/args.rs index 435fbd3b..20016254 100644 --- a/dsc/src/args.rs +++ b/dsc/src/args.rs @@ -4,6 +4,7 @@ use clap::{Parser, Subcommand, ValueEnum}; use clap_complete::Shell; use dsc_lib::dscresources::command_resource::TraceLevel; +use rust_i18n::t; use serde::Deserialize; #[derive(Debug, Clone, PartialEq, Eq, ValueEnum)] @@ -23,33 +24,33 @@ pub enum TraceFormat { } #[derive(Debug, Parser)] -#[clap(name = "dsc", version = env!("CARGO_PKG_VERSION"), about = "Apply configuration or invoke specific DSC resources", long_about = None)] +#[clap(name = "dsc", version = env!("CARGO_PKG_VERSION"), about = t!("args.about").to_string(), long_about = None)] pub struct Args { /// The subcommand to run #[clap(subcommand)] pub subcommand: SubCommand, - #[clap(short = 'l', long, help = "Trace level to use", value_enum)] + #[clap(short = 'l', long, help = t!("args.traceLevel").to_string(), value_enum)] pub trace_level: Option, - #[clap(short = 't', long, help = "Trace format to use", value_enum)] + #[clap(short = 't', long, help = t!("args.traceFormat").to_string(), value_enum)] pub trace_format: Option, } #[derive(Debug, PartialEq, Eq, Subcommand)] pub enum SubCommand { - #[clap(name = "completer", about = "Generate a shell completion script")] + #[clap(name = "completer", about = t!("args.completer").to_string())] Completer { /// The shell to generate a completion script for shell: Shell, }, - #[clap(name = "config", about = "Apply a configuration document")] + #[clap(name = "config", about = t!("args.configAbout").to_string())] Config { #[clap(subcommand)] subcommand: ConfigSubCommand, - #[clap(short, long, help = "Parameters to pass to the configuration as JSON or YAML", conflicts_with = "parameters_file")] + #[clap(short, long, help = t!("args.parameters").to_string(), conflicts_with = "parameters_file")] parameters: Option, - #[clap(short = 'f', long, help = "Parameters to pass to the configuration as a JSON or YAML file", conflicts_with = "parameters")] + #[clap(short = 'f', long, help = t!("args.parametersFile").to_string(), conflicts_with = "parameters")] parameters_file: Option, - #[clap(short = 'r', long, help = "Specify the operating system root path if not targeting the current running OS")] + #[clap(short = 'r', long, help = t!("args.systemRoot").to_string())] system_root: Option, // Used to inform when DSC is used as a group resource to modify it's output #[clap(long, hide = true)] @@ -58,49 +59,49 @@ pub enum SubCommand { #[clap(long, hide = true)] as_include: bool, }, - #[clap(name = "resource", about = "Invoke a specific DSC resource")] + #[clap(name = "resource", about = t!("args.resourceAbout").to_string())] Resource { #[clap(subcommand)] subcommand: ResourceSubCommand, }, - #[clap(name = "schema", about = "Get the JSON schema for a DSC type")] + #[clap(name = "schema", about = t!("args.schemaAbout").to_string())] Schema { - #[clap(name = "type", short, long, help = "The type of DSC schema to get")] + #[clap(name = "type", short, long, help = t!("args.schemaType").to_string(), value_enum)] dsc_type: DscType, - #[clap(short = 'o', long, help = "The output format to use")] + #[clap(short = 'o', long, help = t!("args.outputFormat").to_string(), value_enum)] output_format: Option, }, } #[derive(Debug, PartialEq, Eq, Subcommand)] pub enum ConfigSubCommand { - #[clap(name = "get", about = "Retrieve the current configuration")] + #[clap(name = "get", about = t!("args.getAbout").to_string())] Get { - #[clap(short = 'i', long, help = "The input document as JSON or YAML to pass to the configuration or resource", conflicts_with = "file")] + #[clap(short = 'i', long, help = t!("args.input").to_string(), conflicts_with = "file")] input: Option, - #[clap(short = 'f', long, help = "The path to a file used as input to the configuration or resource. Use '-' for the file to read from STDIN.", conflicts_with = "input")] + #[clap(short = 'f', long, help = t!("args.file").to_string(), conflicts_with = "input")] file: Option, - #[clap(short = 'o', long, help = "The output format to use")] + #[clap(short = 'o', long, help = t!("args.outputFormat").to_string(), value_enum)] output_format: Option, }, - #[clap(name = "set", about = "Set the current configuration")] + #[clap(name = "set", about = t!("args.setAbout").to_string())] Set { - #[clap(short = 'i', long, help = "The input document as JSON or YAML to pass to the configuration or resource", conflicts_with = "file")] + #[clap(short = 'i', long, help = t!("args.input").to_string(), conflicts_with = "file")] input: Option, - #[clap(short = 'f', long, help = "The path to a file used as input to the configuration or resource. Use '-' for the file to read from STDIN.", conflicts_with = "input")] + #[clap(short = 'f', long, help = t!("args.file").to_string(), conflicts_with = "input")] file: Option, - #[clap(short = 'o', long, help = "The output format to use")] + #[clap(short = 'o', long, help = t!("args.outputFormat").to_string())] output_format: Option, - #[clap(short = 'w', long, help = "Run as a what-if operation instead of executing the configuration or resource")] + #[clap(short = 'w', long, help = t!("args.whatIf").to_string())] what_if: bool, }, - #[clap(name = "test", about = "Test the current configuration")] + #[clap(name = "test", about = t!("args.testAbout").to_string())] Test { - #[clap(short = 'i', long, help = "The input document as JSON or YAML to pass to the configuration or resource", conflicts_with = "file")] + #[clap(short = 'i', long, help = t!("args.input").to_string(), conflicts_with = "file")] input: Option, - #[clap(short = 'f', long, help = "The path to a file used as input to the configuration or resource. Use '-' for the file to read from STDIN.", conflicts_with = "input")] + #[clap(short = 'f', long, help = t!("args.file").to_string(), conflicts_with = "input")] file: Option, - #[clap(short = 'o', long, help = "The output format to use")] + #[clap(short = 'o', long, help = t!("args.outputFormat").to_string())] output_format: Option, // Used by Assertion resource to return `test` result as a `get` result #[clap(long, hide = true)] @@ -109,107 +110,107 @@ pub enum ConfigSubCommand { #[clap(long, hide = true)] as_config: bool, }, - #[clap(name = "validate", about = "Validate the current configuration", hide = true)] + #[clap(name = "validate", about = t!("args.validateAbout").to_string(), hide = true)] Validate { - #[clap(short = 'i', long, help = "The document to pass to the configuration or resource", conflicts_with = "file")] + #[clap(short = 'i', long, help = t!("args.input").to_string(), conflicts_with = "file")] input: Option, - #[clap(short = 'f', long, help = "The path to a file used as input to the configuration or resource. Use '-' for the file to read from STDIN.", conflicts_with = "input")] + #[clap(short = 'f', long, help = t!("args.file").to_string(), conflicts_with = "input")] file: Option, - #[clap(short = 'o', long, help = "The output format to use")] + #[clap(short = 'o', long, help = t!("args.outputFormat").to_string())] output_format: Option, }, - #[clap(name = "export", about = "Export the current configuration")] + #[clap(name = "export", about = t!("args.exportAbout").to_string())] Export { - #[clap(short = 'i', long, help = "The document to pass to the configuration or resource", conflicts_with = "file")] + #[clap(short = 'i', long, help = t!("args.input").to_string(), conflicts_with = "file")] input: Option, - #[clap(short = 'f', long, help = "The path to a file used as input to the configuration or resource. Use '-' for the file to read from STDIN.", conflicts_with = "input")] + #[clap(short = 'f', long, help = t!("args.file").to_string(), conflicts_with = "input")] file: Option, - #[clap(short = 'o', long, help = "The output format to use")] + #[clap(short = 'o', long, help = t!("args.outputFormat").to_string())] output_format: Option, }, - #[clap(name = "resolve", about = "Resolve the current configuration", hide = true)] + #[clap(name = "resolve", about = t!("args.resolveAbout").to_string(), hide = true)] Resolve { - #[clap(short = 'i', long, help = "The document to pass to the configuration or resource", conflicts_with = "file")] + #[clap(short = 'i', long, help = t!("args.input").to_string(), conflicts_with = "file")] input: Option, - #[clap(short = 'f', long, help = "The path to a file used as input to the configuration or resource. Use '-' for the file to read from STDIN.", conflicts_with = "input")] + #[clap(short = 'f', long, help = t!("args.file").to_string(), conflicts_with = "input")] file: Option, - #[clap(short = 'o', long, help = "The output format to use")] + #[clap(short = 'o', long, help = t!("args.outputFormat").to_string())] output_format: Option, } } #[derive(Debug, PartialEq, Eq, Subcommand)] pub enum ResourceSubCommand { - #[clap(name = "list", about = "List or find resources")] + #[clap(name = "list", about = t!("args.listAbout").to_string())] List { /// Optional filter to apply to the list of resources resource_name: Option, /// Optional adapter filter to apply to the list of resources - #[clap(short = 'a', long = "adapter", help = "Adapter filter to limit the resource search")] + #[clap(short = 'a', long = "adapter", help = t!("args.adapter").to_string())] adapter_name: Option, - #[clap(short, long, help = "Description keyword to search for in the resource description")] + #[clap(short, long, help = t!("args.description").to_string())] description: Option, - #[clap(short, long, help = "Tag to search for in the resource tags")] + #[clap(short, long, help = t!("args.tags").to_string())] tags: Option>, - #[clap(short = 'o', long, help = "The output format to use")] + #[clap(short = 'o', long, help = t!("args.outputFormat").to_string())] output_format: Option, }, - #[clap(name = "get", about = "Invoke the get operation to a resource", arg_required_else_help = true)] + #[clap(name = "get", about = t!("args.resourceGet").to_string(), arg_required_else_help = true)] Get { - #[clap(short, long, help = "Get all instances of the resource")] + #[clap(short, long, help = t!("args.getAll").to_string())] all: bool, - #[clap(short, long, help = "The name or DscResource JSON of the resource to invoke `get` on")] + #[clap(short, long, help = t!("args.resource").to_string())] resource: String, - #[clap(short, long, help = "The input to pass to the resource as JSON or YAML", conflicts_with = "file")] + #[clap(short, long, help = t!("args.input").to_string(), conflicts_with = "file")] input: Option, - #[clap(short = 'f', long, help = "The path to a JSON or YAML file used as input to the configuration or resource. Use '-' as the file to read from STDIN.", conflicts_with = "input")] + #[clap(short = 'f', long, help = t!("args.file").to_string(), conflicts_with = "input")] file: Option, - #[clap(short = 'o', long, help = "The output format to use")] + #[clap(short = 'o', long, help = t!("args.outputFormat").to_string())] output_format: Option, }, #[clap(name = "set", about = "Invoke the set operation to a resource", arg_required_else_help = true)] Set { - #[clap(short, long, help = "The name or DscResource JSON of the resource to invoke `set` on")] + #[clap(short, long, help = t!("args.resource").to_string())] resource: String, - #[clap(short, long, help = "The input to pass to the resource as JSON or YAML", conflicts_with = "file")] + #[clap(short, long, help = t!("args.input").to_string(), conflicts_with = "file")] input: Option, - #[clap(short = 'f', long, help = "The path to a JSON or YAML file used as input to the configuration or resource. Use '-' for the file to read from STDIN.", conflicts_with = "input")] + #[clap(short = 'f', long, help = t!("args.file").to_string(), conflicts_with = "input")] file: Option, - #[clap(short = 'o', long, help = "The output format to use")] + #[clap(short = 'o', long, help = t!("args.outputFormat").to_string())] output_format: Option, }, #[clap(name = "test", about = "Invoke the test operation to a resource", arg_required_else_help = true)] Test { - #[clap(short, long, help = "The name or DscResource JSON of the resource to invoke `test` on")] + #[clap(short, long, help = t!("args.resource").to_string())] resource: String, - #[clap(short, long, help = "The input to pass to the resource as JSON or YAML", conflicts_with = "file")] + #[clap(short, long, help = t!("args.input").to_string(), conflicts_with = "file")] input: Option, - #[clap(short = 'f', long, help = "The path to a JSON or YAML file used as input to the configuration or resource. Use '-' for the file to read from STDIN.", conflicts_with = "input")] + #[clap(short = 'f', long, help = t!("args.file").to_string(), conflicts_with = "input")] file: Option, - #[clap(short = 'o', long, help = "The output format to use")] + #[clap(short = 'o', long, help = t!("args.outputFormat").to_string())] output_format: Option, }, #[clap(name = "delete", about = "Invoke the delete operation to a resource", arg_required_else_help = true)] Delete { - #[clap(short, long, help = "The name or DscResource JSON of the resource to invoke `delete` on")] + #[clap(short, long, help = t!("args.resource").to_string())] resource: String, - #[clap(short, long, help = "The input to pass to the resource as JSON or YAML", conflicts_with = "file")] + #[clap(short, long, help = t!("args.input").to_string(), conflicts_with = "file")] input: Option, - #[clap(short = 'f', long, help = "The path to a JSON or YAML file used as input to the configuration or resource. Use '-' for the file to read from STDIN.", conflicts_with = "input")] + #[clap(short = 'f', long, help = t!("args.file").to_string(), conflicts_with = "input")] file: Option, }, #[clap(name = "schema", about = "Get the JSON schema for a resource", arg_required_else_help = true)] Schema { - #[clap(short, long, help = "The name of the resource to get the JSON schema")] + #[clap(short, long, help = t!("args.resource").to_string())] resource: String, - #[clap(short = 'o', long, help = "The output format to use")] + #[clap(short = 'o', long, help = t!("args.outputFormat").to_string())] output_format: Option, }, #[clap(name = "export", about = "Retrieve all resource instances", arg_required_else_help = true)] Export { - #[clap(short, long, help = "The name or DscResource JSON of the resource to invoke `export` on")] + #[clap(short, long, help = t!("args.resource").to_string())] resource: String, - #[clap(short = 'o', long, help = "The output format to use")] + #[clap(short = 'o', long, help = t!("args.outputFormat").to_string())] output_format: Option, }, } diff --git a/dsc/src/main.rs b/dsc/src/main.rs index 59a52a75..f3c0d1f2 100644 --- a/dsc/src/main.rs +++ b/dsc/src/main.rs @@ -4,6 +4,7 @@ use args::{Args, SubCommand}; use clap::{CommandFactory, Parser}; use clap_complete::generate; +use rust_i18n::{i18n, t}; use std::{io, process::exit}; use sysinfo::{Process, RefreshKind, System, get_current_pid, ProcessRefreshKind}; use tracing::{error, info, warn, debug}; @@ -20,6 +21,8 @@ pub mod subcommand; pub mod tablewriter; pub mod util; +i18n!("locales", fallback = "en-us"); + fn main() { #[cfg(debug_assertions)] check_debug(); @@ -28,28 +31,28 @@ fn main() { check_store(); if ctrlc::set_handler(ctrlc_handler).is_err() { - error!("Error: Failed to set Ctrl-C handler"); + error!("{}", t!("main.failedCtrlCHandler")); } let args = Args::parse(); util::enable_tracing(args.trace_level.as_ref(), args.trace_format.as_ref()); - debug!("Running dsc {}", env!("CARGO_PKG_VERSION")); + debug!("{}: {}", t!("main.usingDscVersion"), env!("CARGO_PKG_VERSION")); match args.subcommand { SubCommand::Completer { shell } => { - info!("Generating completion script for {:?}", shell); + info!("{} {:?}", t!("main.generatingCompleter"), shell); let mut cmd = Args::command(); generate(shell, &mut cmd, "dsc", &mut io::stdout()); }, SubCommand::Config { subcommand, parameters, parameters_file, system_root, as_group, as_include } => { if let Some(file_name) = parameters_file { - info!("Reading parameters from file {file_name}"); + info!("{}: {file_name}", t!("main.readingParametersFile")); match std::fs::read_to_string(&file_name) { Ok(parameters) => subcommand::config(&subcommand, &Some(parameters), system_root.as_ref(), &as_group, &as_include), Err(err) => { - error!("Error: Failed to read parameters file '{file_name}': {err}"); + error!("{} '{file_name}': {err}", t!("main.failedToReadParametersFile")); exit(util::EXIT_INVALID_INPUT); } } @@ -66,7 +69,7 @@ fn main() { let json = match serde_json::to_string(&schema) { Ok(json) => json, Err(err) => { - error!("JSON Error: {err}"); + error!("JSON: {err}"); exit(util::EXIT_JSON_ERROR); } }; @@ -78,18 +81,18 @@ fn main() { } fn ctrlc_handler() { - warn!("Ctrl-C received"); + warn!("{}", t!("main.ctrlCReceived")); // get process tree for current process and terminate all processes let sys = System::new_with_specifics(RefreshKind::nothing().with_processes(ProcessRefreshKind::everything())); - info!("Found {} processes", sys.processes().len()); + info!("{}: {}", t!("main.foundProcesses"), sys.processes().len()); let Ok(current_pid) = get_current_pid() else { - error!("Could not get current process id"); + error!("{}", t!("main.failedToGetPid")); exit(util::EXIT_CTRL_C); }; - info!("Current process id: {}", current_pid); + info!("{}: {}", t!("main.currentPid"), current_pid); let Some(current_process) = sys.process(current_pid) else { - error!("Could not get current process"); + error!("{}", t!("main.failedToGetProcess")); exit(util::EXIT_CTRL_C); }; @@ -98,14 +101,14 @@ fn ctrlc_handler() { } fn terminate_subprocesses(sys: &System, process: &Process) { - info!("Terminating subprocesses of process {:?} {}", process.name(), process.pid()); + info!("{}: {:?} {}", t!("main.terminatingSubprocess"), process.name(), process.pid()); for subprocess in sys.processes().values().filter(|p| p.parent().map_or(false, |parent| parent == process.pid())) { terminate_subprocesses(sys, subprocess); } - info!("Terminating process {:?} {}", process.name(), process.pid()); + info!("{}: {:?} {}", t!("main.terminatingProcess"), process.name(), process.pid()); if !process.kill() { - error!("Failed to terminate process {:?} {}", process.name(), process.pid()); + error!("{}: {:?} {}", t!("main.failedTerminateProcess"), process.name(), process.pid()); } } @@ -117,7 +120,7 @@ fn check_debug() { let event = match event::read() { Ok(event) => event, Err(err) => { - eprintln!("Error: Failed to read event: {err}"); + eprintln!("Failed to read event: {err}"); break; } }; @@ -126,9 +129,6 @@ fn check_debug() { if key.kind == event::KeyEventKind::Press { break; } - } else { - eprintln!("Unexpected event: {event:?}"); - continue; } } } @@ -139,12 +139,6 @@ fn check_debug() { fn check_store() { use std::io::Read; - let message = r" -DSC.exe is a command-line tool and cannot be run directly from the Windows Store or Explorer. -Visit https://aka.ms/dscv3-docs for more information on how to use DSC.exe. - -Press any key to close this window -"; let sys = System::new_with_specifics(RefreshKind::nothing().with_processes(ProcessRefreshKind::everything())); // get current process let Ok(current_pid) = get_current_pid() else { @@ -164,7 +158,7 @@ Press any key to close this window // MS Store runs app using `sihost.exe` if parent_process.name().to_ascii_lowercase() == "sihost.exe" || parent_process.name().to_ascii_lowercase() == "explorer.exe"{ - eprintln!("{message}"); + eprintln!("{}", t!("main.storeMessage")); // wait for keypress let _ = io::stdin().read(&mut [0u8]).unwrap(); exit(util::EXIT_INVALID_ARGS); diff --git a/dsc/src/resolve.rs b/dsc/src/resolve.rs index 5e261466..711cf661 100644 --- a/dsc/src/resolve.rs +++ b/dsc/src/resolve.rs @@ -3,6 +3,7 @@ use dsc_lib::configure::config_doc::Configuration; use dsc_lib::util::parse_input_to_json; +use rust_i18n::t; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::io::Read; @@ -40,13 +41,13 @@ pub struct Include { /// specified in the Include input cannot be read, or if the content of the file cannot be /// deserialized as YAML or JSON. pub fn get_contents(input: &str) -> Result<(Option, String), String> { - debug!("Processing Include input"); + debug!("{}", t!("resolve.processingInclude")); // deserialize the Include input let include = match serde_json::from_str::(input) { Ok(include) => include, Err(err) => { - return Err(format!("Error: Failed to deserialize Include input: {err}")); + return Err(format!("{}: {err}", t!("resolve.invalidInclude"))); } }; @@ -59,19 +60,19 @@ pub fn get_contents(input: &str) -> Result<(Option, String), String> { match file.read_to_end(&mut buffer) { Ok(_) => (), Err(err) => { - return Err(format!("Error: Failed to read file '{include_path:?}': {err}")); + return Err(format!("{} '{include_path:?}': {err}", t!("resolve.failedToReadFile"))); } } }, Err(err) => { - return Err(format!("Error: Failed to open included file '{include_path:?}': {err}")); + return Err(format!("{} '{include_path:?}': {err}", t!("resolve.failedToOpenFile"))); } } // convert the buffer to a string let include_content = match String::from_utf8(buffer) { Ok(input) => input, Err(err) => { - return Err(format!("Error: Invalid UTF-8 sequence in included file '{include_path:?}': {err}")); + return Err(format!("{} '{include_path:?}': {err}", t!("resolve.invalidFileContent"))); } }; @@ -83,7 +84,7 @@ pub fn get_contents(input: &str) -> Result<(Option, String), String> { match serde_json::from_str(&include_content) { Ok(configuration) => configuration, Err(err) => { - return Err(format!("Error: Failed to read the configuration file '{include_path:?}' as YAML or JSON: {err}")); + return Err(format!("{} '{include_path:?}': {err}", t!("resolve.invalidFile"))); } } } @@ -93,30 +94,30 @@ pub fn get_contents(input: &str) -> Result<(Option, String), String> { let config_json = match serde_json::to_string(&configuration) { Ok(json) => json, Err(err) => { - return Err(format!("Error: JSON Error: {err}")); + return Err(format!("JSON: {err}")); } }; let parameters = if let Some(parameters_file) = include.parameters_file { // combine the path with DSC_CONFIG_ROOT let parameters_file = normalize_path(Path::new(¶meters_file))?; - info!("Resolving parameters from file '{parameters_file:?}'"); + info!("{} '{parameters_file:?}'", t!("resolve.resolvingParameters")); match std::fs::read_to_string(¶meters_file) { Ok(parameters) => { let parameters_json = match parse_input_to_json(¶meters) { Ok(json) => json, Err(err) => { - return Err(format!("Failed to parse parameters file '{parameters_file:?}' to JSON: {err}")); + return Err(format!("{} '{parameters_file:?}': {err}", t!("resolve.failedParseParametersFile"))); } }; Some(parameters_json) }, Err(err) => { - return Err(format!("Failed to resolve parameters file '{parameters_file:?}': {err}")); + return Err(format!("{} '{parameters_file:?}': {err}", t!("resolve.failedResolveParametersFile"))); } } } else { - debug!("No parameters file found"); + debug!("{}", t!("resolve.noParametersFile")); None }; @@ -129,7 +130,7 @@ fn normalize_path(path: &Path) -> Result { } else { // check that no components of the path are '..' if path.components().any(|c| c == std::path::Component::ParentDir) { - return Err(format!("Error: Include path must not contain '..': {path:?}")); + return Err(format!("{}: {path:?}", t!("resolve.invalidPath"))); } // use DSC_CONFIG_ROOT env var as current directory @@ -140,7 +141,7 @@ fn normalize_path(path: &Path) -> Result { match std::env::current_dir() { Ok(current_directory) => current_directory.to_string_lossy().into_owned(), Err(err) => { - return Err(format!("Error: Failed to get current directory: {err}")); + return Err(format!("{}: {err}", t!("resolve.failedGetCurrentDirectory"))); } } } diff --git a/dsc/src/resource_command.rs b/dsc/src/resource_command.rs index 62b10897..0b8aebe0 100644 --- a/dsc/src/resource_command.rs +++ b/dsc/src/resource_command.rs @@ -7,6 +7,7 @@ use dsc_lib::configure::config_doc::{Configuration, ExecutionKind}; use dsc_lib::configure::add_resource_export_results_to_configuration; use dsc_lib::dscresources::{resource_manifest::Kind, invoke_result::{GetResult, ResourceGetResponse}}; use dsc_lib::dscerror::DscError; +use rust_i18n::t; use tracing::{error, debug}; use dsc_lib::{ @@ -21,9 +22,9 @@ pub fn get(dsc: &DscManager, resource_type: &str, mut input: String, format: Opt exit(EXIT_DSC_RESOURCE_NOT_FOUND); }; - debug!("resource.type_name - {} implemented_as - {:?}", resource.type_name, resource.implemented_as); + debug!("{} {} {:?}", resource.type_name, t!("resource_command.implementedAs"), resource.implemented_as); if resource.kind == Kind::Adapter { - error!("Can not perform this operation on the adapter {} itself", resource.type_name); + error!("{}: {}", t!("resource_command.invalidOperationOnAdapter"), resource.type_name); exit(EXIT_DSC_ERROR); } @@ -32,7 +33,7 @@ pub fn get(dsc: &DscManager, resource_type: &str, mut input: String, format: Opt if let Some(pr) = get_resource(dsc, requires) { resource = pr; } else { - error!("Adapter '{}' not found", requires); + error!("{}: {requires}", t!("resource_command.adapterNotFound")); return; }; } @@ -63,9 +64,9 @@ pub fn get_all(dsc: &DscManager, resource_type: &str, format: Option<&OutputForm exit(EXIT_DSC_RESOURCE_NOT_FOUND); }; - debug!("resource.type_name - {} implemented_as - {:?}", resource.type_name, resource.implemented_as); + debug!("{} {} {:?}", resource.type_name, t!("resource_command.implementedAs"), resource.implemented_as); if resource.kind == Kind::Adapter { - error!("Can not perform this operation on the adapter {} itself", resource.type_name); + error!("{}: {}", t!("resource_command.invalidOperationOnAdapter"), resource.type_name); exit(EXIT_DSC_ERROR); } @@ -74,7 +75,7 @@ pub fn get_all(dsc: &DscManager, resource_type: &str, format: Option<&OutputForm if let Some(pr) = get_resource(dsc, requires) { resource = pr; } else { - error!("Adapter '{}' not found", requires); + error!("{}: {requires}", t!("resource_command.adapterNotFound")); return; }; } @@ -106,7 +107,7 @@ pub fn get_all(dsc: &DscManager, resource_type: &str, format: Option<&OutputForm pub fn set(dsc: &DscManager, resource_type: &str, mut input: String, format: Option<&OutputFormat>) { if input.is_empty() { - error!("Error: Desired input is empty"); + error!("{}", t!("resource_command.setInputEmpty")); exit(EXIT_INVALID_ARGS); } @@ -115,9 +116,9 @@ pub fn set(dsc: &DscManager, resource_type: &str, mut input: String, format: Opt exit(EXIT_DSC_RESOURCE_NOT_FOUND); }; - debug!("resource.type_name - {} implemented_as - {:?}", resource.type_name, resource.implemented_as); + debug!("{} {} {:?}", resource.type_name, t!("resource_command.implementedAs"), resource.implemented_as); if resource.kind == Kind::Adapter { - error!("Can not perform this operation on the adapter {} itself", resource.type_name); + error!("{}: {}", t!("resource_command.invalidOperationOnAdapter"), resource.type_name); exit(EXIT_DSC_ERROR); } @@ -126,7 +127,7 @@ pub fn set(dsc: &DscManager, resource_type: &str, mut input: String, format: Opt if let Some(pr) = get_resource(dsc, requires) { resource = pr; } else { - error!("Adapter '{}' not found", requires); + error!("{}: {requires}", t!("resource_command.adapterNotFound")); return; }; } @@ -152,7 +153,7 @@ pub fn set(dsc: &DscManager, resource_type: &str, mut input: String, format: Opt pub fn test(dsc: &DscManager, resource_type: &str, mut input: String, format: Option<&OutputFormat>) { if input.is_empty() { - error!("Error: Expected input is required"); + error!("{}", t!("resource_command.testInputEmpty")); exit(EXIT_INVALID_ARGS); } @@ -161,9 +162,9 @@ pub fn test(dsc: &DscManager, resource_type: &str, mut input: String, format: Op exit(EXIT_DSC_RESOURCE_NOT_FOUND); }; - debug!("resource.type_name - {} implemented_as - {:?}", resource.type_name, resource.implemented_as); + debug!("{} {} {:?}", resource.type_name, t!("resource_command.implementedAs"), resource.implemented_as); if resource.kind == Kind::Adapter { - error!("Can not perform this operation on the adapter {} itself", resource.type_name); + error!("{}: {}", t!("resource_command.invalidOperationOnAdapter"), resource.type_name); exit(EXIT_DSC_ERROR); } @@ -172,7 +173,7 @@ pub fn test(dsc: &DscManager, resource_type: &str, mut input: String, format: Op if let Some(pr) = get_resource(dsc, requires) { resource = pr; } else { - error!("Adapter '{}' not found", requires); + error!("{}: {requires}", t!("resource_command.adapterNotFound")); return; }; } @@ -183,14 +184,14 @@ pub fn test(dsc: &DscManager, resource_type: &str, mut input: String, format: Op let json = match serde_json::to_string(&result) { Ok(json) => json, Err(err) => { - error!("JSON Error: {err}"); + error!("JSON: {err}"); exit(EXIT_JSON_ERROR); } }; write_output(&json, format); } Err(err) => { - error!("Error: {err}"); + error!("{err}"); exit(EXIT_DSC_ERROR); } } @@ -202,9 +203,9 @@ pub fn delete(dsc: &DscManager, resource_type: &str, mut input: String) { exit(EXIT_DSC_RESOURCE_NOT_FOUND); }; - debug!("resource.type_name - {} implemented_as - {:?}", resource.type_name, resource.implemented_as); + debug!("{} {} {:?}", resource.type_name, t!("resource_command.implementedAs"), resource.implemented_as); if resource.kind == Kind::Adapter { - error!("Can not perform this operation on the adapter {} itself", resource.type_name); + error!("{}: {}", t!("resource_command.invalidOperationOnAdapter"), resource.type_name); exit(EXIT_DSC_ERROR); } @@ -213,7 +214,7 @@ pub fn delete(dsc: &DscManager, resource_type: &str, mut input: String) { if let Some(pr) = get_resource(dsc, requires) { resource = pr; } else { - error!("Adapter '{}' not found", requires); + error!("{}: {requires}", t!("resource_command.adapterNotFound")); return; }; } @@ -233,7 +234,7 @@ pub fn schema(dsc: &DscManager, resource_type: &str, format: Option<&OutputForma exit(EXIT_DSC_RESOURCE_NOT_FOUND); }; if resource.kind == Kind::Adapter { - error!("Can not perform this operation on the adapter {} itself", resource.type_name); + error!("{}: {}", t!("resource_command.invalidOperationOnAdapter"), resource.type_name); exit(EXIT_DSC_ERROR); } @@ -264,7 +265,7 @@ pub fn export(dsc: &mut DscManager, resource_type: &str, format: Option<&OutputF }; if dsc_resource.kind == Kind::Adapter { - error!("Can not perform this operation on the adapter {} itself", dsc_resource.type_name); + error!("{}: {}", t!("resource_command.invalidOperationOnAdapter"), dsc_resource.type_name); exit(EXIT_DSC_ERROR); } @@ -274,7 +275,7 @@ pub fn export(dsc: &mut DscManager, resource_type: &str, format: Option<&OutputF if let Some(pr) = get_resource(dsc, requires) { adapter_resource = Some(pr); } else { - error!("Adapter '{}' not found", requires); + error!("{}: {requires}", t!("resource_command.adapterNotFound")); return; }; } @@ -282,14 +283,14 @@ pub fn export(dsc: &mut DscManager, resource_type: &str, format: Option<&OutputF let mut conf = Configuration::new(); if let Err(err) = add_resource_export_results_to_configuration(dsc_resource, adapter_resource, &mut conf, &input) { - error!("Error: {err}"); + error!("{err}"); exit(EXIT_DSC_ERROR); } let json = match serde_json::to_string(&conf) { Ok(json) => json, Err(err) => { - error!("JSON Error: {err}"); + error!("JSON: {err}"); exit(EXIT_JSON_ERROR); } }; diff --git a/dsc/src/subcommand.rs b/dsc/src/subcommand.rs index 32073e6e..25046a8c 100644 --- a/dsc/src/subcommand.rs +++ b/dsc/src/subcommand.rs @@ -26,6 +26,7 @@ use dsc_lib::{ dscresources::dscresource::{Capability, ImplementedAs, Invoke}, dscresources::resource_manifest::{import_manifest, ResourceManifest}, }; +use rust_i18n::t; use std::{ collections::HashMap, io::{self, IsTerminal}, @@ -118,13 +119,13 @@ pub fn config_test(configurator: &mut Configurator, format: Option<&OutputFormat if test_response.actual_state.is_object() { test_response.actual_state.as_object().cloned() } else { - debug!("actual_state is not an object"); + debug!("{}", t!("subcommand.actualStateNotObject")); None } }, TestResult::Group(_) => { // not expected - debug!("Unexpected Group TestResult"); + debug!("{}", t!("subcommand.unexpectedTestResult")); None } }; @@ -140,7 +141,7 @@ pub fn config_test(configurator: &mut Configurator, format: Option<&OutputFormat match serde_json::to_string(&result_configuration) { Ok(json) => json, Err(err) => { - error!("JSON Error: {err}"); + error!("JSON: {err}"); exit(EXIT_JSON_ERROR); } } @@ -153,7 +154,7 @@ pub fn config_test(configurator: &mut Configurator, format: Option<&OutputFormat match serde_json::to_string(&group_result) { Ok(json) => json, Err(err) => { - error!("JSON Error: {err}"); + error!("JSON: {err}"); exit(EXIT_JSON_ERROR); } } @@ -162,7 +163,7 @@ pub fn config_test(configurator: &mut Configurator, format: Option<&OutputFormat match serde_json::to_string(&(result.results)) { Ok(json) => json, Err(err) => { - error!("JSON Error: {err}"); + error!("JSON: {err}"); exit(EXIT_JSON_ERROR); } } @@ -173,7 +174,7 @@ pub fn config_test(configurator: &mut Configurator, format: Option<&OutputFormat let json = match serde_json::to_string(&result) { Ok(json) => json, Err(err) => { - error!("JSON Error: {err}"); + error!("JSON: {err}"); exit(EXIT_JSON_ERROR); } }; @@ -184,7 +185,7 @@ pub fn config_test(configurator: &mut Configurator, format: Option<&OutputFormat } }, Err(err) => { - error!("Error: {err}"); + error!("{err}"); exit(EXIT_DSC_ERROR); } } @@ -197,7 +198,7 @@ pub fn config_export(configurator: &mut Configurator, format: Option<&OutputForm let json = match serde_json::to_string(&result.result) { Ok(json) => json, Err(err) => { - error!("JSON Error: {err}"); + error!("JSON: {err}"); exit(EXIT_JSON_ERROR); } }; @@ -206,14 +207,14 @@ pub fn config_export(configurator: &mut Configurator, format: Option<&OutputForm for msg in result.messages { - error!("{:?} message {}", msg.level, msg.message); + error!("{:?} {} {}", msg.level, t!("subcommand.message"), msg.message); }; exit(EXIT_DSC_ERROR); } }, Err(err) => { - error!("Error: {err}"); + error!("{err}"); exit(EXIT_DSC_ERROR); } } @@ -234,10 +235,10 @@ fn initialize_config_root(path: Option<&String>) -> Option { if std::env::var(DSC_CONFIG_ROOT).is_ok() { let config_root = std::env::var(DSC_CONFIG_ROOT).unwrap_or_default(); - debug!("Using {config_root} for {DSC_CONFIG_ROOT}"); + debug!("DSC_CONFIG_ROOT = {config_root}"); } else { let current_directory = std::env::current_dir().unwrap_or_default(); - debug!("Using current directory '{current_directory:?}' for {DSC_CONFIG_ROOT}"); + debug!("DSC_CONFIG_ROOT = {} '{current_directory:?}'", t!("subcommand.currentDirectory")); set_dscconfigroot(¤t_directory.to_string_lossy()); } @@ -306,11 +307,11 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, mounte parameters } { None => { - debug!("No parameters specified"); + debug!("{}", t!("subcommand.noParameters")); None }, Some(parameters) => { - debug!("Parameters specified"); + debug!("{}", t!("subcommand.parameters")); match serde_json::from_str(parameters) { Ok(json) => Some(json), Err(_) => { @@ -319,13 +320,13 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, mounte match serde_json::to_value(yaml) { Ok(json) => Some(json), Err(err) => { - error!("Error: Failed to convert YAML to JSON: {err}"); + error!("{}: {err}", t!("subcommand.failedConvertJson")); exit(EXIT_DSC_ERROR); } } }, Err(err) => { - error!("Error: Parameters are not valid JSON or YAML: {err}"); + error!("{}: {err}", t!("subcommand.invalidParamters")); exit(EXIT_INVALID_INPUT); } } @@ -336,7 +337,7 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, mounte if let Some(path) = mounted_path { if !Path::new(&path).exists() { - error!("Error: Target path '{path}' does not exist"); + error!("{}: '{path}'", t!("subcommand.invalidPath")); exit(EXIT_INVALID_ARGS); } @@ -344,7 +345,7 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, mounte } if let Err(err) = configurator.set_context(parameters.as_ref()) { - error!("Error: Parameter input failure: {err}"); + error!("{}: {err}", t!("subcommand.failedSetParameters")); exit(EXIT_INVALID_INPUT); } @@ -371,7 +372,7 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, mounte // valid, so do nothing }, Err(err) => { - error!("Error: Failed to deserialize Include input: {err}"); + error!("{}: {err}", t!("subcommand.invalidInclude")); result.valid = false; } } @@ -388,7 +389,7 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, mounte } let Ok(json) = serde_json::to_string(&result) else { - error!("Failed to convert validation result to JSON"); + error!("{}", t!("subcommand.failedSerialize")); exit(EXIT_JSON_ERROR); }; @@ -401,7 +402,7 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, mounte let configuration = match serde_json::from_str(&json_string) { Ok(json) => json, Err(err) => { - error!("Error: Failed to deserialize configuration: {err}"); + error!("{}: {err}", t!("subcommand.invalidConfiguration")); exit(EXIT_DSC_ERROR); } }; @@ -422,7 +423,7 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, mounte let json_string = match serde_json::to_string(&resolve_result) { Ok(json) => json, Err(err) => { - error!("Error: Failed to serialize resolve result: {err}"); + error!("{}: {err}", t!("subcommand.failedSerializeResolve")); exit(EXIT_JSON_ERROR); } }; @@ -446,7 +447,7 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, mounte /// * `DscError` - The error that occurred. pub fn validate_config(config: &Configuration) -> Result<(), DscError> { // first validate against the config schema - debug!("Validating configuration against schema"); + debug!("{}", t!("subcommand.validatingConfiguration")); let schema = serde_json::to_value(get_schema(DscType::Configuration))?; let config_value = serde_json::to_value(config)?; validate_json("Configuration", &schema, &config_value)?; @@ -454,14 +455,14 @@ pub fn validate_config(config: &Configuration) -> Result<(), DscError> { // then validate each resource let Some(resources) = config_value["resources"].as_array() else { - return Err(DscError::Validation("Error: Resources not specified".to_string())); + return Err(DscError::Validation(t!("subcommand.noResources").to_string())); }; // discover the resources let mut resource_types = Vec::new(); for resource_block in resources { let Some(type_name) = resource_block["type"].as_str() else { - return Err(DscError::Validation("Error: Resource type not specified".to_string())); + return Err(DscError::Validation(t!("subcommand.resourceTypeNotSpecified").to_string())); }; if resource_types.contains(&type_name.to_lowercase()) { @@ -474,14 +475,14 @@ pub fn validate_config(config: &Configuration) -> Result<(), DscError> { for resource_block in resources { let Some(type_name) = resource_block["type"].as_str() else { - return Err(DscError::Validation("Error: Resource type not specified".to_string())); + return Err(DscError::Validation(t!("subcommand.resourceTypeNotSpecified").to_string())); }; - trace!("Validating resource named '{}'", resource_block["name"].as_str().unwrap_or_default()); + trace!("{} '{}'", t!("subcommand.validatingResource"), resource_block["name"].as_str().unwrap_or_default()); // get the actual resource let Some(resource) = get_resource(&dsc, type_name) else { - return Err(DscError::Validation(format!("Error: Resource type '{type_name}' not found"))); + return Err(DscError::Validation(format!("{}: '{type_name}'", t!("subcommand.resourceNotFound")))); }; // see if the resource is command based @@ -491,28 +492,28 @@ pub fn validate_config(config: &Configuration) -> Result<(), DscError> { // convert to resource_manifest let manifest: ResourceManifest = serde_json::from_value(manifest)?; if manifest.validate.is_some() { - debug!("Resource {type_name} implements validation"); + debug!("{}: {type_name} ", t!("subcommand.resourceImplementsValidate")); // get the resource's part of the config let resource_config = resource_block["properties"].to_string(); let result = resource.validate(&resource_config)?; if !result.valid { - let reason = result.reason.unwrap_or("No reason provided".to_string()); + let reason = result.reason.unwrap_or(t!("subcommand.noReason").to_string()); let type_name = resource.type_name.clone(); - return Err(DscError::Validation(format!("Resource {type_name} failed validation: {reason}"))); + return Err(DscError::Validation(format!("{}: {type_name} {reason}", t!("subcommand.resourceValidationFailed")))); } } else { // use schema validation - trace!("Resource {type_name} does not implement validation, using schema"); + trace!("{}: {type_name}", t!("subcommand.resourceDoesNotImplementValidate")); let Ok(schema) = resource.schema() else { - return Err(DscError::Validation(format!("Error: Resource {type_name} does not have a schema nor supports validation"))); + return Err(DscError::Validation(format!("{}: {type_name}", t!("subcommand.noSchemaOrValidate")))); }; let schema = serde_json::from_str(&schema)?; validate_json(&resource.type_name, &schema, &resource_block["properties"])?; } } else { - return Err(DscError::Validation(format!("Error: Resource {type_name} does not have a manifest"))); + return Err(DscError::Validation(format!("{}: {type_name}", t!("subcommand.noManifest")))); } } } @@ -570,7 +571,14 @@ pub fn resource(subcommand: &ResourceSubCommand) { fn list_resources(dsc: &mut DscManager, resource_name: Option<&String>, adapter_name: Option<&String>, description: Option<&String>, tags: Option<&Vec>, format: Option<&OutputFormat>) { let mut write_table = false; - let mut table = Table::new(&["Type", "Kind", "Version", "Caps", "RequireAdapter", "Description"]); + let mut table = Table::new(&[ + t!("subcommand.tableHeader_type").to_string().as_ref(), + t!("subcommand.tableheader_kind").to_string().as_ref(), + t!("subcommand.tableheader_version").to_string().as_ref(), + t!("subcommand.tableheader_capabilities").to_string().as_ref(), + t!("subcommand.tableheader_adapter").to_string().as_ref(), + t!("subcommand.tableheader_description").to_string().as_ref(), + ]); if format.is_none() && io::stdout().is_terminal() { // write as table if format is not specified and interactive write_table = true; @@ -599,7 +607,7 @@ fn list_resources(dsc: &mut DscManager, resource_name: Option<&String>, adapter_ let manifest = match import_manifest(resource_manifest.clone()) { Ok(resource_manifest) => resource_manifest, Err(err) => { - error!("Error in manifest for {0}: {err}", resource.type_name); + error!("{} {}: {err}", t!("subcommand.invalidManifest"), resource.type_name); continue; } }; @@ -647,7 +655,7 @@ fn list_resources(dsc: &mut DscManager, resource_name: Option<&String>, adapter_ let json = match serde_json::to_string(&resource) { Ok(json) => json, Err(err) => { - error!("JSON Error: {err}"); + error!("JSON: {err}"); exit(EXIT_JSON_ERROR); } }; diff --git a/dsc/src/util.rs b/dsc/src/util.rs index 4d2f89a2..c1495109 100644 --- a/dsc/src/util.rs +++ b/dsc/src/util.rs @@ -27,6 +27,7 @@ use dsc_lib::{ }; use jsonschema::Validator; use path_absolutize::Absolutize; +use rust_i18n::t; use schemars::{schema_for, schema::RootSchema}; use serde::Deserialize; use serde_json::Value; @@ -93,7 +94,7 @@ pub fn serde_json_value_to_string(json: &serde_json::Value) -> String match serde_json::to_string(&json) { Ok(json_string) => json_string, Err(err) => { - error!("Error: Failed to convert JSON to string: {err}"); + error!("{}: {err}", t!("util.failedToConvertJsonToString")); exit(EXIT_DSC_ERROR); } } @@ -153,7 +154,7 @@ pub fn add_type_name_to_json(json: String, type_name: String) -> String match add_fields_to_json(&j, &map) { Ok(json) => json, Err(err) => { - error!("JSON Error: {err}"); + error!("JSON: {err}"); exit(EXIT_JSON_ERROR); } } @@ -233,14 +234,14 @@ pub fn write_output(json: &str, format: Option<&OutputFormat>) { let value: serde_json::Value = match serde_json::from_str(json) { Ok(value) => value, Err(err) => { - error!("JSON Error: {err}"); + error!("JSON: {err}"); exit(EXIT_JSON_ERROR); } }; match serde_json::to_string_pretty(&value) { Ok(json) => json, Err(err) => { - error!("JSON Error: {err}"); + error!("JSON: {err}"); exit(EXIT_JSON_ERROR); } } @@ -250,14 +251,14 @@ pub fn write_output(json: &str, format: Option<&OutputFormat>) { let value: serde_json::Value = match serde_json::from_str(json) { Ok(value) => value, Err(err) => { - error!("JSON Error: {err}"); + error!("JSON: {err}"); exit(EXIT_JSON_ERROR); } }; match serde_yaml::to_string(&value) { Ok(yaml) => yaml, Err(err) => { - error!("YAML Error: {err}"); + error!("YAML: {err}"); exit(EXIT_JSON_ERROR); } } @@ -329,7 +330,7 @@ pub fn enable_tracing(trace_level_arg: Option<&TraceLevel>, trace_format_arg: Op } } } else { - error!("Could not read 'tracing' setting"); + error!("{}", t!("util.failedToReadTracingSetting")); } // override with DSC_TRACE_LEVEL env var if permitted @@ -342,7 +343,7 @@ pub fn enable_tracing(trace_level_arg: Option<&TraceLevel>, trace_format_arg: Op "DEBUG" => TraceLevel::Debug, "TRACE" => TraceLevel::Trace, _ => { - warn!("Invalid DSC_TRACE_LEVEL value '{level}', defaulting to 'warn'"); + warn!("{}: '{level}'", t!("util.invalidTraceLevel")); TraceLevel::Warn } } @@ -408,7 +409,7 @@ pub fn enable_tracing(trace_level_arg: Option<&TraceLevel>, trace_format_arg: Op drop(default_guard); if tracing::subscriber::set_global_default(subscriber).is_err() { - eprintln!("Unable to set global default tracing subscriber. Tracing is diabled."); + eprintln!("{}", t!("util.failedToSetTracing")); } // set DSC_TRACE_LEVEL for child processes @@ -432,18 +433,18 @@ pub fn enable_tracing(trace_level_arg: Option<&TraceLevel>, trace_format_arg: Op /// /// * `DscError` - The JSON is invalid pub fn validate_json(source: &str, schema: &Value, json: &Value) -> Result<(), DscError> { - debug!("Validating {source} against schema"); + debug!("{}: {source}", t!("util.validatingSchema")); trace!("JSON: {json}"); trace!("Schema: {schema}"); let compiled_schema = match Validator::new(schema) { Ok(compiled_schema) => compiled_schema, Err(err) => { - return Err(DscError::Validation(format!("JSON Schema Compilation Error: {err}"))); + return Err(DscError::Validation(format!("{}: {err}", t!("util.failedToCompileSchema")))); } }; if let Err(err) = compiled_schema.validate(json) { - return Err(DscError::Validation(format!("'{source}' failed validation: {err}"))); + return Err(DscError::Validation(format!("{}: '{source}' {err}", t!("util.validationFailed")))); }; Ok(()) @@ -452,19 +453,19 @@ pub fn validate_json(source: &str, schema: &Value, json: &Value) -> Result<(), D pub fn get_input(input: Option<&String>, file: Option<&String>) -> String { trace!("Input: {input:?}, File: {file:?}"); let value = if let Some(input) = input { - debug!("Reading input from command line parameter"); + debug!("{}", t!("util.readingInput")); // see if user accidentally passed in a file path if Path::new(input).exists() { - error!("Error: Document provided is a file path, use '--file' instead"); + error!("{}", t!("util.inputIsFile")); exit(EXIT_INVALID_INPUT); } input.clone() } else if let Some(path) = file { - debug!("Reading input from file {}", path); + debug!("{} {path}", t!("util.readingInputFromFile")); // check if need to read from STDIN if path == "-" { - info!("Reading input from STDIN"); + info!("{}", t!("util.readingInputFromStdin")); let mut stdin = Vec::::new(); match std::io::stdin().read_to_end(&mut stdin) { Ok(_) => { @@ -473,13 +474,13 @@ pub fn get_input(input: Option<&String>, file: Option<&String>) -> String { input }, Err(err) => { - error!("Error: Invalid utf-8 input: {err}"); + error!("{}: {err}", t!("util.invalidUtf8")); exit(EXIT_INVALID_INPUT); } } }, Err(err) => { - error!("Error: Failed to read input from STDIN: {err}"); + error!("{}: {err}", t!("util.failedToReadStdin")); exit(EXIT_INVALID_INPUT); } } @@ -489,25 +490,25 @@ pub fn get_input(input: Option<&String>, file: Option<&String>) -> String { input }, Err(err) => { - error!("Error: Failed to read input file: {err}"); + error!("{}: {err}", t!("util.failedToReadFile")); exit(EXIT_INVALID_INPUT); } } } } else { - debug!("No input provided"); + debug!("{}", t!("util.noInput")); return String::new(); }; if value.trim().is_empty() { - error!("Provided input is empty"); + error!("{}", t!("util.emptyInput")); exit(EXIT_INVALID_INPUT); } match parse_input_to_json(&value) { Ok(json) => json, Err(err) => { - error!("Error: Invalid JSON or YAML: {err}"); + error!("{}: {err}", t!("util.failedToParseInput")); exit(EXIT_INVALID_INPUT); } } @@ -529,14 +530,14 @@ pub fn set_dscconfigroot(config_path: &str) -> String // make path absolute let Ok(full_path) = path.absolutize() else { - error!("Error making config path absolute"); + error!("{}", t!("util.failedToAbsolutizePath")); exit(EXIT_DSC_ERROR); }; let config_root_path = if full_path.is_file() { let Some(config_root_path) = full_path.parent() else { // this should never happen because path was made absolute - error!("Error reading config path parent"); + error!("{}", t!("util.failedToGetParentPath")); exit(EXIT_DSC_ERROR); }; config_root_path.to_string_lossy().into_owned() @@ -546,11 +547,11 @@ pub fn set_dscconfigroot(config_path: &str) -> String // warn if env var is already set/used if env::var(DSC_CONFIG_ROOT).is_ok() { - warn!("The current value of '{DSC_CONFIG_ROOT}' env var will be overridden"); + warn!("{}", t!("util.dscConfigRootAlreadySet")); } // Set env var so child processes (of resources) can use it - debug!("Setting '{DSC_CONFIG_ROOT}' env var as '{config_root_path}'"); + debug!("{} '{config_root_path}'", t!("util.settingDscConfigRoot")); env::set_var(DSC_CONFIG_ROOT, config_root_path); full_path.to_string_lossy().into_owned() diff --git a/dsc/tests/dsc_args.tests.ps1 b/dsc/tests/dsc_args.tests.ps1 index ec4b2062..e13e216d 100644 --- a/dsc/tests/dsc_args.tests.ps1 +++ b/dsc/tests/dsc_args.tests.ps1 @@ -291,6 +291,6 @@ resources: It 'Invalid --system-root' { dsc config --system-root /invalid/path get -f "$PSScriptRoot/../examples/groups.dsc.yaml" 2> $TestDrive/tracing.txt $LASTEXITCODE | Should -Be 1 - "$TestDrive/tracing.txt" | Should -FileContentMatchExactly "Target path '/invalid/path' does not exist" + "$TestDrive/tracing.txt" | Should -FileContentMatchExactly "Target path does not exist: '/invalid/path'" } }