Skip to content

Commit 2550a41

Browse files
committed
Add dsc function list subcommand to show supported functions
1 parent 518e72e commit 2550a41

35 files changed

+522
-130
lines changed

dsc/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dsc/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ dsc_lib = { path = "../dsc_lib" }
2121
indicatif = { version = "0.18" }
2222
jsonschema = { version = "0.30", default-features = false }
2323
path-absolutize = { version = "3.1" }
24+
regex = "1.11"
2425
rust-i18n = { version = "3.1" }
2526
schemars = { version = "0.8" }
2627
serde = { version = "1.0", features = ["derive"] }

dsc/locales/en-us.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ tags = "Tag to search for in the resource tags"
3232
resourceGet = "Invoke the get operation to a resource"
3333
getAll = "Get all instances of the resource"
3434
resource = "The name of the resource to invoke"
35+
resourceName = "The name or wildcard of the resource to list"
36+
extensionName = "The name or wildcard of the extension to list"
37+
functionAbout = "Operations on DSC functions"
38+
listFunctionAbout = "List or find functions"
39+
functionName = "The name or wildcard of the function to list"
3540

3641
[main]
3742
ctrlCReceived = "Ctrl-C received"
@@ -109,6 +114,13 @@ tableHeader_version = "Version"
109114
tableHeader_capabilities = "Capabilities"
110115
tableHeader_adapter = "RequireAdapter"
111116
tableHeader_description = "Description"
117+
tableHeader_functionName = "Function"
118+
tableHeader_functionCategory = "Category"
119+
tableHeader_minArgs = "MinArgs"
120+
tableHeader_maxArgs = "MaxArgs"
121+
tableHeader_argTypes = "ArgTypes"
122+
invalidFunctionFilter = "Invalid function filter"
123+
maxInt = "maxInt"
112124
invalidManifest = "Error in manifest for"
113125
jsonArrayNotSupported = "JSON array output format is only supported for `--all'"
114126

dsc/src/args.rs

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@ pub enum SubCommand {
8686
#[clap(subcommand)]
8787
subcommand: ExtensionSubCommand,
8888
},
89+
#[clap(name = "function", about = t!("args.functionAbout").to_string())]
90+
Function {
91+
#[clap(subcommand)]
92+
subcommand: FunctionSubCommand,
93+
},
8994
#[clap(name = "resource", about = t!("args.resourceAbout").to_string())]
9095
Resource {
9196
#[clap(subcommand)]
@@ -94,7 +99,7 @@ pub enum SubCommand {
9499
#[clap(name = "schema", about = t!("args.schemaAbout").to_string())]
95100
Schema {
96101
#[clap(name = "type", short, long, help = t!("args.schemaType").to_string(), value_enum)]
97-
dsc_type: DscType,
102+
dsc_type: SchemaType,
98103
#[clap(short = 'o', long, help = t!("args.outputFormat").to_string(), value_enum)]
99104
output_format: Option<OutputFormat>,
100105
},
@@ -170,18 +175,29 @@ pub enum ConfigSubCommand {
170175
pub enum ExtensionSubCommand {
171176
#[clap(name = "list", about = t!("args.listExtensionAbout").to_string())]
172177
List {
173-
/// Optional filter to apply to the list of extensions
178+
#[clap(short = 'n', long, help = t!("args.extensionName").to_string())]
174179
extension_name: Option<String>,
175180
#[clap(short = 'o', long, help = t!("args.outputFormat").to_string())]
176181
output_format: Option<ListOutputFormat>,
177182
},
178183
}
179184

185+
#[derive(Debug, PartialEq, Eq, Subcommand)]
186+
pub enum FunctionSubCommand {
187+
#[clap(name = "list", about = t!("args.listFunctionAbout").to_string())]
188+
List {
189+
#[clap(short = 'n', long, help = t!("args.functionName").to_string())]
190+
function_name: Option<String>,
191+
#[clap(short = 'o', long, help = t!("args.outputFormat").to_string())]
192+
output_format: Option<ListOutputFormat>,
193+
},
194+
}
195+
180196
#[derive(Debug, PartialEq, Eq, Subcommand)]
181197
pub enum ResourceSubCommand {
182198
#[clap(name = "list", about = t!("args.listAbout").to_string())]
183199
List {
184-
/// Optional filter to apply to the list of resources
200+
#[clap(short = 'n', long, help = t!("args.resourceName").to_string())]
185201
resource_name: Option<String>,
186202
/// Optional adapter filter to apply to the list of resources
187203
#[clap(short = 'a', long = "adapter", help = t!("args.adapter").to_string())]
@@ -258,7 +274,7 @@ pub enum ResourceSubCommand {
258274
}
259275

260276
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
261-
pub enum DscType {
277+
pub enum SchemaType {
262278
GetResult,
263279
SetResult,
264280
TestResult,
@@ -271,5 +287,6 @@ pub enum DscType {
271287
ConfigurationSetResult,
272288
ConfigurationTestResult,
273289
ExtensionManifest,
274-
ExtensionDiscoverResult
290+
ExtensionDiscoverResult,
291+
FunctionDefinition,
275292
}

dsc/src/main.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ fn main() {
9292
SubCommand::Extension { subcommand } => {
9393
subcommand::extension(&subcommand, progress_format);
9494
},
95+
SubCommand::Function { subcommand } => {
96+
subcommand::function(&subcommand);
97+
},
9598
SubCommand::Resource { subcommand } => {
9699
subcommand::resource(&subcommand, progress_format);
97100
},

dsc/src/subcommand.rs

Lines changed: 108 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
use crate::args::{ConfigSubCommand, DscType, ExtensionSubCommand, GetOutputFormat, ListOutputFormat, OutputFormat, ResourceSubCommand};
4+
use crate::args::{ConfigSubCommand, SchemaType, ExtensionSubCommand, FunctionSubCommand, GetOutputFormat, ListOutputFormat, OutputFormat, ResourceSubCommand};
55
use crate::resolve::{get_contents, Include};
66
use crate::resource_command::{get_resource, self};
77
use crate::tablewriter::Table;
88
use crate::util::{get_input, get_schema, in_desired_state, set_dscconfigroot, validate_json, write_object, DSC_CONFIG_ROOT, EXIT_DSC_ASSERTION_FAILED, EXIT_DSC_ERROR, EXIT_INVALID_ARGS, EXIT_INVALID_INPUT, EXIT_JSON_ERROR};
9+
use dsc_lib::functions::AcceptedArgKind;
910
use dsc_lib::{
1011
configure::{
1112
config_doc::{
@@ -28,8 +29,11 @@ use dsc_lib::{
2829
dscresources::dscresource::{Capability, ImplementedAs, Invoke},
2930
dscresources::resource_manifest::{import_manifest, ResourceManifest},
3031
extensions::dscextension::Capability as ExtensionCapability,
32+
functions::FunctionDispatcher,
3133
progress::ProgressFormat,
34+
util::convert_wildcard_to_regex,
3235
};
36+
use regex::RegexBuilder;
3337
use rust_i18n::t;
3438
use std::{
3539
collections::HashMap,
@@ -473,7 +477,7 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option<String>, parame
473477
pub fn validate_config(config: &Configuration, progress_format: ProgressFormat) -> Result<(), DscError> {
474478
// first validate against the config schema
475479
debug!("{}", t!("subcommand.validatingConfiguration"));
476-
let schema = serde_json::to_value(get_schema(DscType::Configuration))?;
480+
let schema = serde_json::to_value(get_schema(SchemaType::Configuration))?;
477481
let config_value = serde_json::to_value(config)?;
478482
validate_json("Configuration", &schema, &config_value)?;
479483
let mut dsc = DscManager::new()?;
@@ -562,6 +566,15 @@ pub fn extension(subcommand: &ExtensionSubCommand, progress_format: ProgressForm
562566
}
563567
}
564568

569+
pub fn function(subcommand: &FunctionSubCommand) {
570+
let functions = FunctionDispatcher::new();
571+
match subcommand {
572+
FunctionSubCommand::List { function_name, output_format } => {
573+
list_functions(&functions, function_name.as_ref(), output_format.as_ref());
574+
},
575+
}
576+
}
577+
565578
#[allow(clippy::too_many_lines)]
566579
pub fn resource(subcommand: &ResourceSubCommand, progress_format: ProgressFormat) {
567580
let mut dsc = match DscManager::new() {
@@ -632,10 +645,10 @@ fn list_extensions(dsc: &mut DscManager, extension_name: Option<&String>, format
632645
let mut include_separator = false;
633646
for manifest_resource in dsc.list_available(&DiscoveryKind::Extension, extension_name.unwrap_or(&String::from("*")), "", progress_format) {
634647
if let ImportedManifest::Extension(extension) = manifest_resource {
635-
let mut capabilities = "-".to_string();
636648
let capability_types = [
637649
(ExtensionCapability::Discover, "d"),
638650
];
651+
let mut capabilities = "-".repeat(capability_types.len());
639652

640653
for (i, (capability, letter)) in capability_types.iter().enumerate() {
641654
if extension.capabilities.contains(capability) {
@@ -680,6 +693,97 @@ fn list_extensions(dsc: &mut DscManager, extension_name: Option<&String>, format
680693
}
681694
}
682695

696+
fn list_functions(functions: &FunctionDispatcher, function_name: Option<&String>, output_format: Option<&ListOutputFormat>) {
697+
let mut write_table = false;
698+
let mut table = Table::new(&[
699+
t!("subcommand.tableHeader_functionCategory").to_string().as_ref(),
700+
t!("subcommand.tableHeader_functionName").to_string().as_ref(),
701+
t!("subcommand.tableHeader_minArgs").to_string().as_ref(),
702+
t!("subcommand.tableHeader_maxArgs").to_string().as_ref(),
703+
t!("subcommand.tableHeader_argTypes").to_string().as_ref(),
704+
t!("subcommand.tableHeader_description").to_string().as_ref(),
705+
]);
706+
if output_format.is_none() && io::stdout().is_terminal() {
707+
// write as table if format is not specified and interactive
708+
write_table = true;
709+
}
710+
let mut include_separator = false;
711+
let accepted_arg_types= [
712+
(AcceptedArgKind::Array, "a"),
713+
(AcceptedArgKind::Boolean, "b"),
714+
(AcceptedArgKind::Number, "n"),
715+
(AcceptedArgKind::String, "s"),
716+
(AcceptedArgKind::Object, "o"),
717+
];
718+
719+
let asterisks = String::from("*");
720+
let name = function_name.unwrap_or(&asterisks);
721+
let regex_str = convert_wildcard_to_regex(name);
722+
let mut regex_builder = RegexBuilder::new(&regex_str);
723+
regex_builder.case_insensitive(true);
724+
let Ok(regex) = regex_builder.build() else {
725+
error!("{}: {}", t!("subcommand.invalidFunctionFilter"), regex_str);
726+
exit(EXIT_INVALID_ARGS);
727+
};
728+
729+
let mut functions_list = functions.list();
730+
functions_list.sort();
731+
for function in functions_list.into_iter() {
732+
if !regex.is_match(&function.name) {
733+
continue;
734+
}
735+
736+
if write_table {
737+
// construct arg_types from '-' times number of accepted_arg_types
738+
let mut arg_types = "-".repeat(accepted_arg_types.len());
739+
for (i, (arg_type, letter)) in accepted_arg_types.iter().enumerate() {
740+
if function.accepted_arg_types.contains(arg_type) {
741+
arg_types.replace_range(i..=i, letter);
742+
}
743+
}
744+
745+
let max_args = if function.max_args == usize::MAX {
746+
t!("subcommand.maxInt").to_string()
747+
} else {
748+
function.max_args.to_string()
749+
};
750+
751+
table.add_row(vec![
752+
function.category.to_string(),
753+
function.name,
754+
function.min_args.to_string(),
755+
max_args,
756+
arg_types,
757+
function.description
758+
]);
759+
}
760+
else {
761+
let json = match serde_json::to_string(&function) {
762+
Ok(json) => json,
763+
Err(err) => {
764+
error!("JSON: {err}");
765+
exit(EXIT_JSON_ERROR);
766+
}
767+
};
768+
let format = match output_format {
769+
Some(ListOutputFormat::Json) => Some(OutputFormat::Json),
770+
Some(ListOutputFormat::PrettyJson) => Some(OutputFormat::PrettyJson),
771+
Some(ListOutputFormat::Yaml) => Some(OutputFormat::Yaml),
772+
_ => None,
773+
};
774+
write_object(&json, format.as_ref(), include_separator);
775+
include_separator = true;
776+
// insert newline separating instances if writing to console
777+
if io::stdout().is_terminal() { println!(); }
778+
}
779+
}
780+
781+
if write_table {
782+
let truncate = output_format != Some(&ListOutputFormat::TableNoTruncate);
783+
table.print(truncate);
784+
}
785+
}
786+
683787
fn list_resources(dsc: &mut DscManager, resource_name: Option<&String>, adapter_name: Option<&String>, description: Option<&String>, tags: Option<&Vec<String>>, format: Option<&ListOutputFormat>, progress_format: ProgressFormat) {
684788
let mut write_table = false;
685789
let mut table = Table::new(&[
@@ -697,7 +801,6 @@ fn list_resources(dsc: &mut DscManager, resource_name: Option<&String>, adapter_
697801
let mut include_separator = false;
698802
for manifest_resource in dsc.list_available(&DiscoveryKind::Resource, resource_name.unwrap_or(&String::from("*")), adapter_name.unwrap_or(&String::new()), progress_format) {
699803
if let ImportedManifest::Resource(resource) = manifest_resource {
700-
let mut capabilities = "--------".to_string();
701804
let capability_types = [
702805
(Capability::Get, "g"),
703806
(Capability::Set, "s"),
@@ -708,6 +811,7 @@ fn list_resources(dsc: &mut DscManager, resource_name: Option<&String>, adapter_
708811
(Capability::Export, "e"),
709812
(Capability::Resolve, "r"),
710813
];
814+
let mut capabilities = "-".repeat(capability_types.len());
711815

712816
for (i, (capability, letter)) in capability_types.iter().enumerate() {
713817
if resource.capabilities.contains(capability) {

dsc/src/util.rs

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
use crate::args::{DscType, OutputFormat, TraceFormat};
4+
use crate::args::{SchemaType, OutputFormat, TraceFormat};
55
use crate::resolve::Include;
66
use dsc_lib::configure::config_result::ResourceTestResult;
77
use dsc_lib::extensions::discover::DiscoverResult;
@@ -25,6 +25,7 @@ use dsc_lib::{
2525
ResolveResult,
2626
}, resource_manifest::ResourceManifest
2727
},
28+
functions::FunctionDefinition,
2829
util::parse_input_to_json,
2930
util::get_setting,
3031
};
@@ -143,47 +144,50 @@ pub fn add_fields_to_json(json: &str, fields_to_add: &HashMap<String, String>) -
143144
///
144145
/// * `RootSchema` - The schema
145146
#[must_use]
146-
pub fn get_schema(dsc_type: DscType) -> RootSchema {
147-
match dsc_type {
148-
DscType::GetResult => {
147+
pub fn get_schema(schema: SchemaType) -> RootSchema {
148+
match schema {
149+
SchemaType::GetResult => {
149150
schema_for!(GetResult)
150151
},
151-
DscType::SetResult => {
152+
SchemaType::SetResult => {
152153
schema_for!(SetResult)
153154
},
154-
DscType::TestResult => {
155+
SchemaType::TestResult => {
155156
schema_for!(TestResult)
156157
},
157-
DscType::ResolveResult => {
158+
SchemaType::ResolveResult => {
158159
schema_for!(ResolveResult)
159160
}
160-
DscType::DscResource => {
161+
SchemaType::DscResource => {
161162
schema_for!(DscResource)
162163
},
163-
DscType::ResourceManifest => {
164+
SchemaType::ResourceManifest => {
164165
schema_for!(ResourceManifest)
165166
},
166-
DscType::Include => {
167+
SchemaType::Include => {
167168
schema_for!(Include)
168169
},
169-
DscType::Configuration => {
170+
SchemaType::Configuration => {
170171
schema_for!(Configuration)
171172
},
172-
DscType::ConfigurationGetResult => {
173+
SchemaType::ConfigurationGetResult => {
173174
schema_for!(ConfigurationGetResult)
174175
},
175-
DscType::ConfigurationSetResult => {
176+
SchemaType::ConfigurationSetResult => {
176177
schema_for!(ConfigurationSetResult)
177178
},
178-
DscType::ConfigurationTestResult => {
179+
SchemaType::ConfigurationTestResult => {
179180
schema_for!(ConfigurationTestResult)
180181
},
181-
DscType::ExtensionManifest => {
182+
SchemaType::ExtensionManifest => {
182183
schema_for!(ExtensionManifest)
183184
},
184-
DscType::ExtensionDiscoverResult => {
185+
SchemaType::ExtensionDiscoverResult => {
185186
schema_for!(DiscoverResult)
186187
},
188+
SchemaType::FunctionDefinition => {
189+
schema_for!(FunctionDefinition)
190+
}
187191
}
188192
}
189193

0 commit comments

Comments
 (0)