From 7e84d88efc5a378d0b551fd6aab7d780135a82f8 Mon Sep 17 00:00:00 2001 From: Mikey Lombardi Date: Thu, 24 Oct 2024 15:13:30 -0500 Subject: [PATCH 1/3] Canonicalize registry resource schema This change prototypes shifting from separately maintained schemas in the `schemas` folder to deriving fully expressed schemas from the Rust structs in the code. Doing so requires using the alpha release of schemars v1, which added support for more easily defining schemas using attributes and supports deriving schemas adhering to the JSON Schema 2020-12 specification. Of particular note are the following changes to the JSON Schema for instances of the registry resource: - Use JSON Schema specification draft 2020-12 instead of draft 07. - Added `title` and `description` for every property as documentation. - Added a regular expression check for `keyPath` to ensure it starts with a valid hive identifier. - Added the `$id` keyword to ensure that users and integrating developers can get a copy of the schema online instead of always calling the command. - Marked the `keyPath` property as required. - Marked the `valueName` property as required _only when_ `valueData` is specified. - Marked the `_metadata` property and its `whatIf` sub-property as read-only. - Defined the default value for the `_exist` property as `true`. In the process of implementing this prototype, this change also addressed a few related challenges: - In order to define longer strings for documentation keywords in the schemas, this change uses the `rust_i18n` library to provide a way to map complex or lengthy strings to key names. This also prepares the project for internationalization and localization, though the change only defines translations for English. - To minimize rework, the struct attributes define the JSON Schema with VS Code's annotation keywords, which enhance the authoring and editing experience in VS Code, but are generally ignored by other tools and libraries. To account for the difference between the canonical (stictly 2020-12 compatible) schema and the enhanced schema this change also: 1. Adds the `--enhanced` option to the `registry schema` command to enable returning the enhanced schema, but default to returning the canonical schema as is current behavior. 1. Defines the `remove_vscode_keywords` function to recursively remove those keywords. While the function is implemented in the registry resource in this prototyping PR, it better belongs in `dsc_lib`. 1. Defines the `get_schema_generator` function to return a generator with the correct settings for canonical and enhanced schemas. In both cases, it no longer adds the `null` type for optional items in the struct to better conform with JSON Schema. In JSON Schema, specifying `null` for a property value is _not_ semantically the same as _not_ specifying the property. For the enhanced schemas, it also uses `definitions` instead of `$defs`, as VS Code doesn't understand retrieving references from `$defs`. This change shows how we can begin to: 1. Define full, enhanced JSON schemas for data types in Rust and export those schemas in both canonical and enhanced variants. 1. Migrate from in-code strings to referencing data keys, making it easier to change documentation and messages without editing the code and preparing the projects for internationalization and localization. 1. Define reusable helpers for the projects to minimize rework. The scope of these changes is limited to the registry project only. --- registry/Cargo.lock | 406 +++++++++++++++++++++++++++++++++-- registry/Cargo.toml | 11 +- registry/locales/cli.yml | 10 + registry/locales/schemas.yml | 169 +++++++++++++++ registry/src/args.rs | 7 +- registry/src/config.rs | 102 ++++++++- registry/src/main.rs | 18 +- registry/src/schema.rs | 87 ++++++++ 8 files changed, 784 insertions(+), 26 deletions(-) create mode 100644 registry/locales/cli.yml create mode 100644 registry/locales/schemas.yml create mode 100644 registry/src/schema.rs diff --git a/registry/Cargo.lock b/registry/Cargo.lock index 58896fbfa..618130ab0 100644 --- a/registry/Cargo.lock +++ b/registry/Cargo.lock @@ -47,7 +47,7 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -57,15 +57,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.52.0", ] +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + [[package]] name = "autocfg" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +[[package]] +name = "base62" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f879ef8fc74665ed7f0e6127cb106315888fc2744f68e14b74f83edbb2a08992" + [[package]] name = "bitflags" version = "1.3.2" @@ -78,6 +90,16 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "bstr" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -130,6 +152,31 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + [[package]] name = "crossterm" version = "0.28.1" @@ -161,6 +208,18 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.3.9" @@ -168,9 +227,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" + +[[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.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + [[package]] name = "heck" version = "0.5.0" @@ -183,12 +284,47 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[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 = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "is_terminal_polyfill" 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" @@ -207,6 +343,12 @@ version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +[[package]] +name = "libyml" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64804cc6a5042d4f05379909ba25b503ec04e2c082151d62122d5dcaa274b961" + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -254,7 +396,16 @@ dependencies = [ "libc", "log", "wasi", - "windows-sys", + "windows-sys 0.52.0", +] + +[[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]] @@ -335,16 +486,36 @@ dependencies = [ "bitflags 2.6.0", ] +[[package]] +name = "ref-cast" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf0a6f84d5f1d581da8b41b47ec8600871962f2a528115b542b362d4b744931" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "regex" -version = "1.10.6" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", ] [[package]] @@ -358,13 +529,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", ] [[package]] @@ -375,9 +546,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "registry" @@ -385,7 +556,9 @@ version = "1.0.0" dependencies = [ "clap", "crossterm", + "regex", "registry 1.2.3", + "rust-i18n", "schemars", "serde", "serde_json", @@ -409,6 +582,60 @@ dependencies = [ "winapi", ] +[[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 = "rustix" version = "0.38.37" @@ -419,7 +646,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -428,13 +655,23 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schemars" -version = "0.8.21" +version = "1.0.0-alpha.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" +checksum = "1848710f31190edab07d4dfee50b89cb4429a789c4aa9aa07356fbe59c59af52" dependencies = [ "dyn-clone", + "ref-cast", "schemars_derive", "serde", "serde_json", @@ -442,9 +679,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.21" +version = "1.0.0-alpha.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" +checksum = "09196e95ccdbafef196a660dc9380e322a222c73341162b611f0a8f32cc02375" dependencies = [ "proc-macro2", "quote", @@ -501,6 +738,32 @@ 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_yml" +version = "0.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e76bab63c3fd98d27c17f9cbce177f64a91f5e69ac04cafe04e1bb25d1dc3c" +dependencies = [ + "indexmap", + "itoa", + "libyml", + "log", + "memchr", + "ryu", + "serde", + "serde_json", + "tempfile", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -540,12 +803,24 @@ 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "static_vcruntime" version = "2.0.0" @@ -569,6 +844,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "thiserror" version = "1.0.64" @@ -599,6 +887,40 @@ dependencies = [ "once_cell", ] +[[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", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tracing" version = "0.1.40" @@ -673,6 +995,17 @@ dependencies = [ "tracing-serde", ] +[[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-ident" version = "1.0.13" @@ -697,6 +1030,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -719,6 +1062,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -734,6 +1086,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -797,3 +1158,12 @@ name = "windows_x86_64_msvc" 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", +] diff --git a/registry/Cargo.toml b/registry/Cargo.toml index b8c8a8f64..0d6eb2ed5 100644 --- a/registry/Cargo.toml +++ b/registry/Cargo.toml @@ -3,6 +3,11 @@ name = "registry" version = "1.0.0" edition = "2021" +[package.metadata.i18n] +available-locales = ["en"] +default-locale = "en" +load-path = "locales" + [profile.release] strip = true # optimize for size @@ -13,9 +18,11 @@ lto = true [dependencies] clap = { version = "4.5", features = ["derive"] } crossterm = "0.28" +regex = "1.11.0" registry = "1.2" -schemars = "0.8" -serde = "1.0" +rust-i18n = "3" +schemars = "1.0.0-alpha.15" +serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" thiserror = "1.0" tracing = { version = "0.1" } diff --git a/registry/locales/cli.yml b/registry/locales/cli.yml new file mode 100644 index 000000000..638bc8900 --- /dev/null +++ b/registry/locales/cli.yml @@ -0,0 +1,10 @@ +_version: 2 +cli: + # schema command help + schema: + about: + en: Retrieve JSON schema. + args: + enhanced: + help: + en: Retrieve JSON schema enhanced for VS Code authoring and review. diff --git a/registry/locales/schemas.yml b/registry/locales/schemas.yml new file mode 100644 index 000000000..c66e98e24 --- /dev/null +++ b/registry/locales/schemas.yml @@ -0,0 +1,169 @@ +_version: 2 + +schema: + title: + en: Microsoft.Windows/Registry instance + description: + en: Manages Windows Registry keys and values. + markdownDescription: + en: |- + Each instance of the `Microsoft.Windows/Registry` resource manages a registry key or value. + You can use it to idempotently ensure that a registry key or value exists and set the data + for registry values. + # Properties + keyPath: + title: + en: Registry key path + description: + en: >- + Defines the path to the registry key to manage. Must be prefixed with a valid + hive identifier. Separate the paths with back slashes only. + markdownDescription: + en: |- + The `keyPath` property defines the path to the registry key an instance + of the resource manages. It must be prefixed with a valid hive identifier: + + | Identifier | Hive | + |:-------------------------------:|:----------------------| + | `HKCC` or `HKEY_CURRENT_CONFIG` | Current configuration | + | `HKCU` or `HKEY_CURRENT_USER` | Current user | + | `HKCR` or `HKEY_CLASSES_ROOT` | Classes root | + | `HKLM` or `HKEY_LOCAL_MACHINE` | Local machine | + | `HKU` or `HKEY_USERS` | Users | + + Specify the path to the registry key with backslashes (`\`), like + `HKCU\DSC\Properties\keyPath\Example`. If using JSON, ensure the + backlsashes are escaped, like `HKCU\\DSC\\Properties\\keyPath\\Example`. + patternErrorMessage: + en: >- + The value must begin with a valid hive identifier. Valid identifiers are: + + HKCC, HKEY_CURRENT_CONFIG, + HKCU, HKEY_CURRENT_USER + HKCR, HKEY_CLASSES_ROOT + HKLM, HKEY_LOCAL_MACHINE + HKU, and HKEY_USERS. + + metadata: + title: + en: Resource metadata + description: + en: Defines metadata returned by the resource. + markdownDescription: + en: |- + The `metadata` property defines metadata returned by the resource. The registry resource + doesn't support sending metadata input. It returns messages when invoked in what-if mode. + whatIf: + title: + en: What-if messages + description: + en: A list of messages returned by the resource when invoked in what-if mode. + markdownDescription: + en: >- + Contains a list of messages returned by the resource when invoked in what-if mode, as + with the `dsc config set --what-if` command. + valueName: + title: + en: Registry value name + description: + en: Defines the name of the registry value to manage. + markdownDescription: + en: |- + The `valueName` property defines the name of the registry value to manage. This value is + required when specifying value data. + valueData: + title: + en: Registry value data + description: + en: Defines the data for a registry value. + markdownDescription: + en: |- + Defines the data for a registry value. When you specify this value, you must also specify + the name of the registry value. + + Define the data as an object with a single property. The property name determines the data + type and the property value determines the data value. The following table lists the + available data types. + + | Property name | Data description | + |:--------------:|:-------------------------------------------------------------------| + | `String` | Defines the registry value as a static string. | + | `ExpandString` | Defines the registry value as a string with expandable references. | + | `MultiString` | Defines the registry value as an array of static strings. | + | `Binary` | Defines the registry value as an array of bytes. | + | `DWord` | Defines the registry value as an unsigned 32-bit integer. | + | `QWord` | Defines the registry value as an unsigned 64-bit integer. | + String: + title: + en: String value data + description: + en: Defines the registry value as a static string. + markdownDescription: + en: |- + When you define `valueData` with the `String` property, the resource sets the data for + the registry value as a static string. If the string contains any unexpanded references + to environment variables, such as `%PATH%`, the references are treated as literal + characters and aren't expandable.` + ExpandString: + title: + en: Expandable string value data + description: + en: Defines the registry value as a string with expandable references. + markdownDescription: + en: |- + When you define `valueData` with the `ExpandString` property, the resource sets the data + for the registry value as a string. If the string contains any unexpanded references to + environment variables, such as `%PATH%`, they're expandable. + MultiString: + title: + en: Multiple string value data + description: + en: Defines the registry value as an array of static strings. + markdownDescription: + en: |- + When you define `valueData` with the `MultiString` property, the resource sets the data + for the registry value as an array of static strings. You can specify the value as an + empty array, or as an array containing one or more strings. If any of the strings in the + array contain any unexpanded references to environment variables, such as `%PATH%`, the + references are treated as literal characters and aren't expandable. + Binary: + title: + en: Binary value data + description: + en: Defines the registry value as an array of bytes. + markdownDescription: + en: |- + When you define `valueData` with the `Binary` property, the resource sets the data for + the registry value as an array of bytes. You can specify the value as an empty array or + as an array containing the integer representation of one or more bytes. + DWord: + title: + en: DWord value data + description: + en: Defines the registry value as an unsigned 32-bit integer. + markdownDescription: + en: |- + When you define `valueData` with the `DWord` property, the resource sets the data for the + registry value as an unsigned 32-bit integer. + QWord: + title: + en: QWord value data + description: + en: Defines the registry value as an unsigned 64-bit integer. + markdownDescription: + en: |- + When you define `valueData` with the `QWord` property, the resource sets the data for the + registry value as an unsigned 64-bit integer. + exist: + title: + en: Exist + description: + en: Defines whether the instance should exist. + markdownDescription: + en: |- + The `_exist` property defines whether a registry key or value should exist. When this + property is `true`, the resource creates the registry key or value if it doesn't exist + during a `set` operation. When this property is `false`, the resource deletes the registry + key or value if it exists during a `set` operation. + + The default value is `true`. diff --git a/registry/src/args.rs b/registry/src/args.rs index a0521ec9f..96c498f7c 100644 --- a/registry/src/args.rs +++ b/registry/src/args.rs @@ -77,6 +77,9 @@ pub enum SubCommand { #[clap(subcommand)] subcommand: ConfigSubCommand, }, - #[clap(name = "schema", about = "Retrieve JSON schema.")] - Schema, + #[clap(name = "schema", about = t!("cli.schema.about").to_string())] + Schema { + #[clap(short, long, help = t!("cli.schema.args.values_only.help").to_string())] + enhanced: bool, + }, } diff --git a/registry/src/config.rs b/registry/src/config.rs index a18fee2d1..2815f518f 100644 --- a/registry/src/config.rs +++ b/registry/src/config.rs @@ -3,33 +3,125 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use crate::schema::{get_registry_key_path_pattern, get_schema_uri}; #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)] pub enum RegistryValueData { + #[schemars( + title = t!("schema.valueData.String.title").to_string(), + description = t!("schema.valueData.String.description").to_string(), + extend( + "markdownDescription" = t!("schema.valueData.String.markdownDescription").to_string() + ) + )] String(String), + #[schemars( + title = t!("schema.valueData.ExpandString.title").to_string(), + description = t!("schema.valueData.ExpandString.description").to_string(), + extend( + "markdownDescription" = t!("schema.valueData.ExpandString.markdownDescription").to_string() + ) + )] ExpandString(String), + #[schemars( + title = t!("schema.valueData.Binary.title").to_string(), + description = t!("schema.valueData.Binary.description").to_string(), + extend( + "markdownDescription" = t!("schema.valueData.Binary.markdownDescription").to_string() + ) + )] Binary(Vec), + #[schemars( + title = t!("schema.valueData.DWord.title").to_string(), + description = t!("schema.valueData.DWord.description").to_string(), + extend( + "markdownDescription" = t!("schema.valueData.DWord.markdownDescription").to_string() + ) + )] DWord(u32), + #[schemars( + title = t!("schema.valueData.MultiString.title").to_string(), + description = t!("schema.valueData.MultiString.description").to_string(), + extend( + "markdownDescription" = t!("schema.valueData.MultiString.markdownDescription").to_string() + ) + )] MultiString(Vec), + #[schemars( + title = t!("schema.valueData.QWord.title").to_string(), + description = t!("schema.valueData.QWord.description").to_string(), + extend( + "markdownDescription" = t!("schema.valueData.QWord.markdownDescription").to_string() + ) + )] QWord(u64), } #[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)] #[serde(rename = "Registry", deny_unknown_fields)] +#[schemars( + title=t!("schema.title").to_string(), + description=t!("schema.description").to_string(), + extend( + "$id" = get_schema_uri(true), + "dependentRequired" = { "valueData": ["valueName"] }, + "markdownDescription" = t!("schema.markdownDescription").to_string() + ) +)] pub struct Registry { /// The path to the registry key. #[serde(rename = "keyPath")] + #[schemars( + title = t!("schema.keyPath.title").to_string(), + description = t!("schema.keyPath.description").to_string(), + required, + regex(pattern = get_registry_key_path_pattern()), + extend( + "markdownDescription" = t!("schema.keyPath.markdownDescription").to_string(), + "patternErrorMessage" = t!("schema.keyPath.patternErrorMessage").to_string(), + ) + )] pub key_path: String, /// The information from a config set --what-if operation. #[serde(rename = "_metadata", skip_serializing_if = "Option::is_none")] + #[schemars( + title = t!("schema.metadata.title").to_string(), + description = t!("schema.metadata.description").to_string(), + extend( + "readOnly" = true, + "markdownDescription" = t!("schema.metadata.markdownDescription").to_string() + ) + )] pub metadata: Option, /// The name of the registry value. #[serde(rename = "valueName", skip_serializing_if = "Option::is_none")] + #[schemars( + title = t!("schema.valueName.title").to_string(), + description = t!("schema.valueName.description").to_string(), + extend( + "markdownDescription" = t!("schema.valueName.markdownDescription").to_string() + ) + )] pub value_name: Option, /// The data of the registry value. #[serde(rename = "valueData", skip_serializing_if = "Option::is_none")] + #[schemars( + title = t!("schema.valueData.title").to_string(), + description = t!("schema.valueData.description").to_string(), + extend( + "markdownDescription" = t!("schema.valueData.markdownDescription").to_string() + ) + )] pub value_data: Option, - #[serde(rename = "_exist", skip_serializing_if = "Option::is_none")] + #[serde(rename = "_exist")] + #[schemars( + title = t!("schema.exist.title").to_string(), + description = t!("schema.exist.description").to_string(), + extend( + "default" = true, + "markdownDescription" = t!("schema.exist.markdownDescription").to_string() + ) + )] pub exist: Option, } @@ -37,5 +129,13 @@ pub struct Registry { #[serde(deny_unknown_fields)] pub struct Metadata { #[serde(rename = "whatIf", skip_serializing_if = "Option::is_none")] + #[schemars( + title = t!("schema.metadata.whatIf.title").to_string(), + description = t!("schema.metadata.whatIf.description").to_string(), + extend( + "readOnly" = true, + "markdownDescription" = t!("schema.metadata.whatIf.markdownDescription").to_string() + ) + )] pub what_if: Option> } diff --git a/registry/src/main.rs b/registry/src/main.rs index 7b078d79c..da554d433 100644 --- a/registry/src/main.rs +++ b/registry/src/main.rs @@ -6,10 +6,16 @@ use crossterm::event; #[cfg(debug_assertions)] use std::env; +#[macro_use] +extern crate rust_i18n; + +// Init translations using the `[package.metadata.i18n]` section in `Cargo.toml` +i18n!(); + use args::Arguments; use clap::Parser; use registry_helper::RegistryHelper; -use schemars::schema_for; +use schema::{canonicalize_schema, get_schema_generator}; use std::process::exit; use tracing::{debug, error}; use tracing_subscriber::{filter::LevelFilter, prelude::__tracing_subscriber_SubscriberExt, EnvFilter, Layer}; @@ -19,6 +25,7 @@ mod args; pub mod config; mod error; mod registry_helper; +mod schema; const EXIT_SUCCESS: i32 = 0; const EXIT_INVALID_INPUT: i32 = 2; @@ -111,8 +118,13 @@ fn main() { }, } }, - args::SubCommand::Schema => { - let schema = schema_for!(Registry); + args::SubCommand::Schema { enhanced } => { + let generator = get_schema_generator(enhanced); + let mut schema = generator.into_root_schema_for::(); + if !enhanced { + // Set to canonical schema URI and remove VS Code keywords + canonicalize_schema(&mut schema); + } let json =serde_json::to_string(&schema).unwrap(); println!("{json}"); }, diff --git a/registry/src/schema.rs b/registry/src/schema.rs new file mode 100644 index 000000000..988f94901 --- /dev/null +++ b/registry/src/schema.rs @@ -0,0 +1,87 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use schemars::generate::SchemaSettings; +use schemars::transform::transform_subschemas; +use schemars::{Schema, SchemaGenerator}; +use regex::Regex; + + +/// Returns the canonical URI for the resource instance schema. +pub fn get_schema_uri(vscode: bool) -> String { + const URI_PREFIX: &str = "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/resources"; + const URI_BASE: &str = "Microsoft.Windows/Registry"; + const URI_VERSION: &str = env!("CARGO_PKG_VERSION"); + let uri_suffix = if vscode { "schema.vscode.json" } else { "schema.json" }; + + format!("{}/{}/v{}/{}", URI_PREFIX, URI_BASE, URI_VERSION, uri_suffix) +} + +/// Returns a regular expression to validate the `keyPath` resource instance property. +pub fn get_registry_key_path_pattern() -> Regex { + let prefixes: Vec = vec![ + "HKCC".to_string(), "HKEY_CURRENT_CONFIG".to_string(), + "HKCU".to_string(), "HKEY_CURRENT_USER".to_string(), + "HKCR".to_string(), "HKEY_CLASSES_ROOT".to_string(), + "HKLM".to_string(), "HKEY_LOCAL_MACHINE".to_string(), + "HKU".to_string(), "HKEY_USERS".to_string() + ]; + + Regex::new( + format!(r"^({})\\[a-zA-Z0-9-_\\]+?[^\\]$", prefixes.join("|")).as_str() + ).unwrap() +} + +/// Munges the JSON Schema to use the correct URI for the schema ID and removes VS Code keywords. +pub fn canonicalize_schema(schema: &mut Schema) { + // Update `$schema` to appropriate JSON file for 2020-12, without VS Code keywords + schema.insert("$id".to_string(), serde_json::Value::String(get_schema_uri(false))); + remove_vscode_keywords(schema); +} + +// This should more properly move to DSC for reuse +/// Recursively removes keywords from the JSON Schema that are only used and understood by VS Code +/// to provide an enhanced authoring and editing experience. The VS Code keywords are annotations +/// that most JSON Schema tools and implementations _should_ ignore, but may still cause issues. +pub fn remove_vscode_keywords(schema: &mut Schema) { + let keywords: Vec = vec![ + "defaultSnippets".to_string(), + "errorMessage".to_string(), + "patternErrorMessage".to_string(), + "deprecationMessage".to_string(), + "enumDescriptions".to_string(), + "markdownEnumDescriptions".to_string(), + "markdownDescription".to_string(), + "doNotSuggest".to_string(), + "suggestSortText".to_string(), + "allowComments".to_string(), + "allowTrailingCommas".to_string(), + ]; + for keyword in keywords { + schema.remove(&keyword); + } + + transform_subschemas(&mut remove_vscode_keywords, schema) +} + +// This should more properly move to DSC for reuse +/// Returns a `SchemaGenerator` for deriving JSON Schemas from structs. If the function is +/// called to return a generator for JSON Schemas meant for use in VS Code, it uses the +/// deprecated `definitions` keyword instead of `$defs` for references, which VS Code +/// doesn't understand yet. +pub fn get_schema_generator(for_vscode_schema: bool) -> SchemaGenerator { + let settings: SchemaSettings = if for_vscode_schema { + SchemaSettings::draft2020_12().with(|s| { + // More properly, the data should not be included in the object, instead of allowing + // or expecting null values + s.option_add_null_type = false; + // Need to use definitions instead of $defs, which VS Code doesn't understand. + s.definitions_path = "/definitions".to_string(); + }) + } else { + SchemaSettings::draft2020_12().with(|s| { + s.option_add_null_type = false; + }) + }; + settings.into_generator() +} From 6f45c260e70cf28b5836ae13d5ce81b8680cc20c Mon Sep 17 00:00:00 2001 From: Mikey Lombardi Date: Thu, 24 Oct 2024 15:54:18 -0500 Subject: [PATCH 2/3] Export registry schemas This change exports the registry schemas to the repository for online review without requiring a user or integrating developer to install the package and invoke the `registry schema` command. --- .../Registry/v1.0.0/schema.json | 163 ++++++++++++++++ .../Registry/v1.0.0/schema.vscode.json | 177 ++++++++++++++++++ 2 files changed, 340 insertions(+) create mode 100644 schemas/resources/Microsoft.Windows/Registry/v1.0.0/schema.json create mode 100644 schemas/resources/Microsoft.Windows/Registry/v1.0.0/schema.vscode.json diff --git a/schemas/resources/Microsoft.Windows/Registry/v1.0.0/schema.json b/schemas/resources/Microsoft.Windows/Registry/v1.0.0/schema.json new file mode 100644 index 000000000..aa09c027e --- /dev/null +++ b/schemas/resources/Microsoft.Windows/Registry/v1.0.0/schema.json @@ -0,0 +1,163 @@ +{ + "$id": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/resources/Microsoft.Windows/Registry/v1.0.0/schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Microsoft.Windows/Registry instance", + "description": "Manages Windows Registry keys and values.", + "type": "object", + "properties": { + "_exist": { + "title": "Exist", + "description": "Defines whether the instance should exist.", + "type": "boolean", + "default": true + }, + "_metadata": { + "title": "Resource metadata", + "description": "Defines metadata returned by the resource.", + "$ref": "#/$defs/Metadata", + "readOnly": true + }, + "keyPath": { + "title": "Registry key path", + "description": "Defines the path to the registry key to manage. Must be prefixed with a valid hive identifier. Separate the paths with back slashes only.", + "type": "string", + "pattern": "^(HKCC|HKEY_CURRENT_CONFIG|HKCU|HKEY_CURRENT_USER|HKCR|HKEY_CLASSES_ROOT|HKLM|HKEY_LOCAL_MACHINE|HKU|HKEY_USERS)\\\\[a-zA-Z0-9-_\\\\]+?[^\\\\]$" + }, + "valueData": { + "title": "Registry value data", + "description": "Defines the data for a registry value.", + "$ref": "#/$defs/RegistryValueData" + }, + "valueName": { + "title": "Registry value name", + "description": "Defines the name of the registry value to manage.", + "type": "string" + } + }, + "additionalProperties": false, + "dependentRequired": { + "valueData": [ + "valueName" + ] + }, + "required": [ + "keyPath" + ], + "$defs": { + "Metadata": { + "type": "object", + "properties": { + "whatIf": { + "title": "What-if messages", + "description": "A list of messages returned by the resource when invoked in what-if mode.", + "type": "array", + "items": { + "type": "string" + }, + "readOnly": true + } + }, + "additionalProperties": false + }, + "RegistryValueData": { + "oneOf": [ + { + "title": "String value data", + "description": "Defines the registry value as a static string.", + "type": "object", + "properties": { + "String": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "String" + ] + }, + { + "title": "Expandable string value data", + "description": "Defines the registry value as a string with expandable references.", + "type": "object", + "properties": { + "ExpandString": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "ExpandString" + ] + }, + { + "title": "Binary value data", + "description": "Defines the registry value as an array of bytes.", + "type": "object", + "properties": { + "Binary": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0 + } + } + }, + "additionalProperties": false, + "required": [ + "Binary" + ] + }, + { + "title": "DWord value data", + "description": "Defines the registry value as an unsigned 32-bit integer.", + "type": "object", + "properties": { + "DWord": { + "type": "integer", + "format": "uint32", + "minimum": 0 + } + }, + "additionalProperties": false, + "required": [ + "DWord" + ] + }, + { + "title": "Multiple string value data", + "description": "Defines the registry value as an array of static strings.", + "type": "object", + "properties": { + "MultiString": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false, + "required": [ + "MultiString" + ] + }, + { + "title": "QWord value data", + "description": "Defines the registry value as an unsigned 64-bit integer.", + "type": "object", + "properties": { + "QWord": { + "type": "integer", + "format": "uint64", + "minimum": 0 + } + }, + "additionalProperties": false, + "required": [ + "QWord" + ] + } + ] + } + } +} diff --git a/schemas/resources/Microsoft.Windows/Registry/v1.0.0/schema.vscode.json b/schemas/resources/Microsoft.Windows/Registry/v1.0.0/schema.vscode.json new file mode 100644 index 000000000..002b6f864 --- /dev/null +++ b/schemas/resources/Microsoft.Windows/Registry/v1.0.0/schema.vscode.json @@ -0,0 +1,177 @@ +{ + "$id": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/resources/Microsoft.Windows/Registry/v1.0.0/schema.vscode.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Microsoft.Windows/Registry instance", + "description": "Manages Windows Registry keys and values.", + "type": "object", + "properties": { + "_exist": { + "title": "Exist", + "description": "Defines whether the instance should exist.", + "type": "boolean", + "default": true, + "markdownDescription": "The `_exist` property defines whether a registry key or value should exist. When this\nproperty is `true`, the resource creates the registry key or value if it doesn't exist\nduring a `set` operation. When this property is `false`, the resource deletes the registry\nkey or value if it exists during a `set` operation.\n\nThe default value is `true`." + }, + "_metadata": { + "title": "Resource metadata", + "description": "Defines metadata returned by the resource.", + "$ref": "#/definitions/Metadata", + "markdownDescription": "The `metadata` property defines metadata returned by the resource. The registry resource\ndoesn't support sending metadata input. It returns messages when invoked in what-if mode.", + "readOnly": true + }, + "keyPath": { + "title": "Registry key path", + "description": "Defines the path to the registry key to manage. Must be prefixed with a valid hive identifier. Separate the paths with back slashes only.", + "type": "string", + "markdownDescription": "The `keyPath` property defines the path to the registry key an instance\nof the resource manages. It must be prefixed with a valid hive identifier:\n\n| Identifier | Hive |\n|:-------------------------------:|:----------------------|\n| `HKCC` or `HKEY_CURRENT_CONFIG` | Current configuration |\n| `HKCU` or `HKEY_CURRENT_USER` | Current user |\n| `HKCR` or `HKEY_CLASSES_ROOT` | Classes root |\n| `HKLM` or `HKEY_LOCAL_MACHINE` | Local machine |\n| `HKU` or `HKEY_USERS` | Users |\n\nSpecify the path to the registry key with backslashes (`\\`), like\n`HKCU\\DSC\\Properties\\keyPath\\Example`. If using JSON, ensure the\nbacklsashes are escaped, like `HKCU\\\\DSC\\\\Properties\\\\keyPath\\\\Example`.", + "pattern": "^(HKCC|HKEY_CURRENT_CONFIG|HKCU|HKEY_CURRENT_USER|HKCR|HKEY_CLASSES_ROOT|HKLM|HKEY_LOCAL_MACHINE|HKU|HKEY_USERS)\\\\[a-zA-Z0-9-_\\\\]+?[^\\\\]$", + "patternErrorMessage": "The value must begin with a valid hive identifier. Valid identifiers are:\nHKCC, HKEY_CURRENT_CONFIG, HKCU, HKEY_CURRENT_USER HKCR, HKEY_CLASSES_ROOT HKLM, HKEY_LOCAL_MACHINE HKU, and HKEY_USERS." + }, + "valueData": { + "title": "Registry value data", + "description": "Defines the data for a registry value.", + "$ref": "#/definitions/RegistryValueData", + "markdownDescription": "Defines the data for a registry value. When you specify this value, you must also specify\nthe name of the registry value.\n\nDefine the data as an object with a single property. The property name determines the data\ntype and the property value determines the data value. The following table lists the\navailable data types.\n\n| Property name | Data description |\n|:--------------:|:-------------------------------------------------------------------|\n| `String` | Defines the registry value as a static string. |\n| `ExpandString` | Defines the registry value as a string with expandable references. |\n| `MultiString` | Defines the registry value as an array of static strings. |\n| `Binary` | Defines the registry value as an array of bytes. |\n| `DWord` | Defines the registry value as an unsigned 32-bit integer. |\n| `QWord` | Defines the registry value as an unsigned 64-bit integer. |" + }, + "valueName": { + "title": "Registry value name", + "description": "Defines the name of the registry value to manage.", + "type": "string", + "markdownDescription": "The `valueName` property defines the name of the registry value to manage. This value is\nrequired when specifying value data." + } + }, + "additionalProperties": false, + "dependentRequired": { + "valueData": [ + "valueName" + ] + }, + "markdownDescription": "Each instance of the `Microsoft.Windows/Registry` resource manages a registry key or value.\nYou can use it to idempotently ensure that a registry key or value exists and set the data\nfor registry values.", + "required": [ + "keyPath" + ], + "definitions": { + "Metadata": { + "type": "object", + "properties": { + "whatIf": { + "title": "What-if messages", + "description": "A list of messages returned by the resource when invoked in what-if mode.", + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "Contains a list of messages returned by the resource when invoked in what-if mode, as with the `dsc config set --what-if` command.", + "readOnly": true + } + }, + "additionalProperties": false + }, + "RegistryValueData": { + "oneOf": [ + { + "title": "String value data", + "description": "Defines the registry value as a static string.", + "type": "object", + "properties": { + "String": { + "type": "string" + } + }, + "additionalProperties": false, + "markdownDescription": "When you define `valueData` with the `String` property, the resource sets the data for\nthe registry value as a static string. If the string contains any unexpanded references\nto environment variables, such as `%PATH%`, the references are treated as literal\ncharacters and aren't expandable.`", + "required": [ + "String" + ] + }, + { + "title": "Expandable string value data", + "description": "Defines the registry value as a string with expandable references.", + "type": "object", + "properties": { + "ExpandString": { + "type": "string" + } + }, + "additionalProperties": false, + "markdownDescription": "When you define `valueData` with the `ExpandString` property, the resource sets the data\nfor the registry value as a string. If the string contains any unexpanded references to\nenvironment variables, such as `%PATH%`, they're expandable.", + "required": [ + "ExpandString" + ] + }, + { + "title": "Binary value data", + "description": "Defines the registry value as an array of bytes.", + "type": "object", + "properties": { + "Binary": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0 + } + } + }, + "additionalProperties": false, + "markdownDescription": "When you define `valueData` with the `Binary` property, the resource sets the data for\nthe registry value as an array of bytes. You can specify the value as an empty array or\nas an array containing the integer representation of one or more bytes.", + "required": [ + "Binary" + ] + }, + { + "title": "DWord value data", + "description": "Defines the registry value as an unsigned 32-bit integer.", + "type": "object", + "properties": { + "DWord": { + "type": "integer", + "format": "uint32", + "minimum": 0 + } + }, + "additionalProperties": false, + "markdownDescription": "When you define `valueData` with the `DWord` property, the resource sets the data for the\nregistry value as an unsigned 32-bit integer.", + "required": [ + "DWord" + ] + }, + { + "title": "Multiple string value data", + "description": "Defines the registry value as an array of static strings.", + "type": "object", + "properties": { + "MultiString": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false, + "markdownDescription": "When you define `valueData` with the `MultiString` property, the resource sets the data\nfor the registry value as an array of static strings. You can specify the value as an\nempty array, or as an array containing one or more strings. If any of the strings in the\narray contain any unexpanded references to environment variables, such as `%PATH%`, the\nreferences are treated as literal characters and aren't expandable.", + "required": [ + "MultiString" + ] + }, + { + "title": "QWord value data", + "description": "Defines the registry value as an unsigned 64-bit integer.", + "type": "object", + "properties": { + "QWord": { + "type": "integer", + "format": "uint64", + "minimum": 0 + } + }, + "additionalProperties": false, + "markdownDescription": "When you define `valueData` with the `QWord` property, the resource sets the data for the\nregistry value as an unsigned 64-bit integer.", + "required": [ + "QWord" + ] + } + ] + } + } +} From f1a1fccd8e6ffa8f2296160663ca663435e5ae31 Mon Sep 17 00:00:00 2001 From: Mikey Lombardi Date: Thu, 24 Oct 2024 15:55:57 -0500 Subject: [PATCH 3/3] Update CLI docs to use i18n --- registry/locales/cli.yml | 81 ++++++++++++++++++++++++++++++++++++++++ registry/src/args.rs | 52 +++++++++++++------------- 2 files changed, 107 insertions(+), 26 deletions(-) diff --git a/registry/locales/cli.yml b/registry/locales/cli.yml index 638bc8900..ae97ad820 100644 --- a/registry/locales/cli.yml +++ b/registry/locales/cli.yml @@ -1,5 +1,86 @@ _version: 2 cli: + about: + en: Manage state of Windows registry + # config command help + config: + about: + en: Manage registry configuration. + args: + input: + help: + en: The registry JSON input. + what_if: + help: + en: Run as a what-if operation instead of applying the registry configuration. + get: + about: + en: Retrieve registry configuration. + set: + about: + en: Apply registry configuration. + delete: + about: + en: Delete registry configuration. + # query command help + query: + about: + en: Query a registry key or value. + args: + key_path: + help: + en: The registry key path to query. + value_name: + help: + en: The name of the value to query. + recurse: + help: + en: Recursively query subkeys. + # set command help + set: + about: + en: Set a registry key or value. + args: + key_path: + help: + en: The registry key path to set. + value: + help: + en: The value to set. + # remove command help + remove: + about: + en: Remove a registry key or value. + args: + key_path: + help: + en: The registry key path to remove. + value_name: + help: + en: The name of the value to remove. + recurse: + help: + en: Recursively remove subkeys. + # find command help + find: + about: + en: Find a registry key or value. + args: + key_path: + help: + en: The registry key path to start find. + find: + help: + en: The string to find. + recurse: + help: + en: Recursively find. + keys_only: + help: + en: Only find keys. + values_only: + help: + en: Only find values. # schema command help schema: about: diff --git a/registry/src/args.rs b/registry/src/args.rs index 96c498f7c..0104ac8b6 100644 --- a/registry/src/args.rs +++ b/registry/src/args.rs @@ -4,7 +4,7 @@ use clap::{Parser, Subcommand}; #[derive(Parser)] -#[clap(name = "registry", version = "0.0.1", about = "Manage state of Windows registry", long_about = None)] +#[clap(name = "registry", version = "0.0.1", about = t!("cli.about").to_string(), long_about = None)] pub struct Arguments { #[clap(subcommand)] @@ -13,66 +13,66 @@ pub struct Arguments { #[derive(Debug, PartialEq, Eq, Subcommand)] pub enum ConfigSubCommand { - #[clap(name = "get", about = "Retrieve registry configuration.")] + #[clap(name = "get", about = t!("cli.config.get.about").to_string())] Get { - #[clap(short, long, required = true, help = "The registry JSON input.")] + #[clap(short, long, required = true, help = t!("cli.config.args.input.help").to_string())] input: String, }, - #[clap(name = "set", about = "Apply registry configuration.")] + #[clap(name = "set", about = t!("cli.config.set.about").to_string())] Set { - #[clap(short, long, required = true, help = "The registry JSON input.")] + #[clap(short, long, required = true, help = t!("cli.config.args.input.help").to_string())] input: String, - #[clap(short = 'w', long, help = "Run as a what-if operation instead of applying the registry configuration")] + #[clap(short = 'w', long, help = t!("cli.config.args.what_if.help").to_string())] what_if: bool, }, - #[clap(name = "delete", about = "Delete registry configuration.")] + #[clap(name = "delete", about = t!("cli.config.delete.about").to_string())] Delete { - #[clap(short, long, required = true, help = "The registry JSON input.")] + #[clap(short, long, required = true, help = t!("cli.config.args.input.help").to_string())] input: String, }, } #[derive(Debug, PartialEq, Eq, Subcommand)] pub enum SubCommand { - #[clap(name = "query", about = "Query a registry key or value.", arg_required_else_help = true)] + #[clap(name = "query", about = t!("cli.query.about").to_string(), arg_required_else_help = true)] Query { - #[clap(short, long, required = true, help = "The registry key path to query.")] + #[clap(short, long, required = true, help = t!("cli.query.args.key_path.help").to_string())] key_path: String, - #[clap(short, long, help = "The name of the value to query.")] + #[clap(short, long, help = t!("cli.query.args.value_name.help").to_string())] value_name: Option, - #[clap(short, long, help = "Recursively query subkeys.")] + #[clap(short, long, help = t!("cli.query.args.recurse.help").to_string())] recurse: bool, }, - #[clap(name = "set", about = "Set a registry key or value.")] + #[clap(name = "set", about = t!("cli.set.about").to_string())] Set { - #[clap(short, long, required = true, help = "The registry key path to set.")] + #[clap(short, long, required = true, help = t!("cli.set.args.key_path.help").to_string())] key_path: String, - #[clap(short, long, help = "The value to set.")] + #[clap(short, long, help = t!("cli.set.args.value.help").to_string())] value: String, }, - #[clap(name = "remove", about = "Remove a registry key or value.", arg_required_else_help = true)] + #[clap(name = "remove", about = t!("cli.remove.about").to_string(), arg_required_else_help = true)] Remove { - #[clap(short, long, required = true, help = "The registry key path to remove.")] + #[clap(short, long, required = true, help = t!("cli.remove.args.key_path.help").to_string())] key_path: String, - #[clap(short, long, help = "The name of the value to remove.")] + #[clap(short, long, help = t!("cli.remove.args.value_name.help").to_string())] value_name: Option, - #[clap(short, long, help = "Recursively remove subkeys.")] + #[clap(short, long, help = t!("cli.remove.args.recurse.help").to_string())] recurse: bool, }, - #[clap(name = "find", about = "Find a registry key or value.", arg_required_else_help = true)] + #[clap(name = "find", about = t!("cli.find.about").to_string(), arg_required_else_help = true)] Find { - #[clap(short, long, required = true, help = "The registry key path to start find.")] + #[clap(short, long, required = true, help = t!("cli.find.args.key_path.help").to_string())] key_path: String, - #[clap(short, long, required = true, help = "The string to find.")] + #[clap(short, long, required = true, help = t!("cli.find.args.find.help").to_string())] find: String, - #[clap(short, long, help = "Recursively find.")] + #[clap(short, long, help = t!("cli.find.args.recurse.help").to_string())] recurse: bool, - #[clap(long, help = "Only find keys.")] + #[clap(long, help = t!("cli.find.args.keys_only.help").to_string())] keys_only: bool, - #[clap(long, help = "Only find values.")] + #[clap(long, help = t!("cli.find.args.values_only.help").to_string())] values_only: bool, }, - #[clap(name = "config", about = "Manage registry configuration.", arg_required_else_help = true)] + #[clap(name = "config", about = t!("cli.config.about").to_string(), arg_required_else_help = true)] Config { #[clap(subcommand)] subcommand: ConfigSubCommand,