Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement use of tree-sitter parser #241

Merged
merged 13 commits into from
Oct 26, 2023
18 changes: 11 additions & 7 deletions build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,18 @@ New-Item -ItemType Directory $target > $null

# make sure dependencies are built first so clippy runs correctly
$windows_projects = @("pal", "ntreg", "ntstatuserror", "ntuserinfo", "registry")

# projects are in dependency order
$projects = @(
"tree-sitter-dscexpression"
"dsc_lib"
"file_lib"
"dsc"
"osinfo"
"process"
"tools/test_group_resource"
"tree-sitter-dscexpression",
"dsc_lib",
"file_lib",
"dsc",
"osinfo",
"powershellgroup",
"process",
"tools/dsctest",
"tools/test_group_resource",
"y2j"
"powershellgroup"
"resources/brew"
Expand Down
20 changes: 7 additions & 13 deletions dsc/src/resource_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,12 +162,9 @@ pub fn export(dsc: &mut DscManager, resource: &str, format: &Option<OutputFormat

let mut conf = Configuration::new();

match add_resource_export_results_to_configuration(&dsc_resource, &mut conf) {
Ok(()) => (),
Err(err) => {
error!("Error: {err}");
exit(EXIT_DSC_ERROR);
}
if let Err(err) = add_resource_export_results_to_configuration(&dsc_resource, &mut conf) {
error!("Error: {err}");
exit(EXIT_DSC_ERROR);
}

let json = match serde_json::to_string(&conf) {
Expand All @@ -190,13 +187,10 @@ pub fn get_resource(dsc: &mut DscManager, resource: &str) -> DscResource {
exit(EXIT_INVALID_ARGS);
}

match dsc.initialize_discovery() {
Ok(()) => (),
Err(err) => {
error!("Error: {err}");
exit(EXIT_DSC_ERROR);
}
};
if let Err(err) = dsc.initialize_discovery() {
error!("Error: {err}");
exit(EXIT_DSC_ERROR);
}
let resources: Vec<DscResource> = dsc.find_resource(resource).collect();
match resources.len() {
0 => {
Expand Down
34 changes: 14 additions & 20 deletions dsc/src/subcommand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use dsc_lib::{
dscresources::dscresource::{ImplementedAs, Invoke},
dscresources::resource_manifest::{import_manifest, ResourceManifest},
};
use jsonschema::{JSONSchema, ValidationError};
use jsonschema::JSONSchema;
use serde_yaml::Value;
use std::process::exit;

Expand Down Expand Up @@ -272,18 +272,15 @@ pub fn validate_config(config: &str) {
},
};
let properties = resource_block["properties"].clone();
let _result: Result<(), ValidationError> = match compiled_schema.validate(&properties) {
Ok(()) => Ok(()),
Err(err) => {
let mut error = String::new();
for e in err {
error.push_str(&format!("{e} "));
}

error!("Error: Resource {type_name} failed validation: {error}");
exit(EXIT_VALIDATION_FAILED);
},
};
let validation = compiled_schema.validate(&properties);
if let Err(err) = validation {
let mut error = String::new();
for e in err {
error.push_str(&format!("{e} "));
}
error!("Error: Resource {type_name} failed validation: {error}");
exit(EXIT_VALIDATION_FAILED);
}
}
}
}
Expand All @@ -303,13 +300,10 @@ pub fn resource(subcommand: &ResourceSubCommand, format: &Option<OutputFormat>,

match subcommand {
ResourceSubCommand::List { resource_name, description, tags } => {
match dsc.initialize_discovery() {
Ok(()) => (),
Err(err) => {
error!("Error: {err}");
exit(EXIT_DSC_ERROR);
}
};
if let Err(err) = dsc.initialize_discovery() {
error!("Error: {err}");
exit(EXIT_DSC_ERROR);
}
let mut write_table = false;
let mut table = Table::new(&["Type", "Version", "Requires", "Description"]);
if format.is_none() && atty::is(Stream::Stdout) {
Expand Down
6 changes: 6 additions & 0 deletions dsc_lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ version = "3.0.0-alpha.4"
edition = "2021"

[dependencies]
base64 = "0.21"
derive_builder ="0.12"
jsonschema = "0.17"
regex = "1.7"
Expand All @@ -14,6 +15,11 @@ serde_json = { version = "1.0", features = ["preserve_order"] }
thiserror = "1.0"
chrono = "0.4.26"
tracing = "0.1.37"
tree-sitter = "~0.20.10"
tree-sitter-dscexpression = { path = "../tree-sitter-dscexpression" }

[dev-dependencies]
serde_yaml = "0.9"

[build-dependencies]
cc="*"
1 change: 1 addition & 0 deletions dsc_lib/src/configure/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ impl Configurator {
pub fn new(config: &str) -> Result<Configurator, DscError> {
let mut discovery = Discovery::new()?;
discovery.initialize()?;

Ok(Configurator {
config: config.to_owned(),
discovery,
Expand Down
21 changes: 21 additions & 0 deletions dsc_lib/src/dscerror.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use std::str::Utf8Error;

use reqwest::StatusCode;
use thiserror::Error;
use chrono::{Local, DateTime};
use tracing::{error, warn};
use tree_sitter::LanguageError;

#[derive(Error, Debug)]
pub enum DscError {
#[error("Function boolean argument conversion error: {0}")]
BooleanConversion(#[from] std::str::ParseBoolError),

#[error("Command: Resource '{0}' [Exit code {1}] {2}")]
Command(String, i32, String),

Expand All @@ -20,6 +26,9 @@ pub enum DscError {
#[error("HTTP status: {0}")]
HttpStatus(StatusCode),

#[error("Function integer argument conversion error: {0}")]
IntegerConversion(#[from] std::num::ParseIntError),

#[error("Regex: {0}")]
Regex(#[from] regex::Error),

Expand All @@ -29,12 +38,18 @@ pub enum DscError {
#[error("Unsupported manifest version: {0}. Must be: {1}")]
InvalidManifestSchemaVersion(String, String),

#[error("Invalid function parameter count for '{0}', expected {1}, got {2}")]
InvalidFunctionParameterCount(String, usize, usize),

#[error("IO: {0}")]
Io(#[from] std::io::Error),

#[error("JSON: {0}")]
Json(#[from] serde_json::Error),

#[error("Language: {0}")]
Language(#[from] LanguageError),

#[error("Manifest: {0}\nJSON: {1}")]
Manifest(String, serde_json::Error),

Expand All @@ -53,6 +68,9 @@ pub enum DscError {
#[error("Operation: {0}")]
Operation(String),

#[error("Parser: {0}")]
Parser(String),

#[error("Resource not found: {0}")]
ResourceNotFound(String),

Expand All @@ -62,6 +80,9 @@ pub enum DscError {
#[error("No Schema: {0}")]
SchemaNotAvailable(String),

#[error("Utf-8 conversion error: {0}")]
Utf8Conversion(#[from] Utf8Error),

#[error("Unknown: {code:?} {message:?}")]
Unknown {
code: i32,
Expand Down
20 changes: 9 additions & 11 deletions dsc_lib/src/dscresources/command_resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -492,18 +492,16 @@ fn verify_json(resource: &ResourceManifest, cwd: &str, json: &str) -> Result<(),
},
};
let json: Value = serde_json::from_str(json)?;
let result = match compiled_schema.validate(&json) {
Ok(()) => Ok(()),
Err(err) => {
let mut error = String::new();
for e in err {
error.push_str(&format!("{e} "));
}
if let Err(err) = compiled_schema.validate(&json) {
let mut error = String::new();
for e in err {
error.push_str(&format!("{e} "));
}

Err(DscError::Schema(error))
},
};
result
return Err(DscError::Schema(error));
}

Ok(())
}

fn json_to_hashmap(json: &str) -> Result<HashMap<String, String>, DscError> {
Expand Down
58 changes: 58 additions & 0 deletions dsc_lib/src/functions/base64.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use base64::{Engine as _, engine::general_purpose};

use crate::DscError;
use crate::parser::functions::{FunctionArg, FunctionResult};
use super::{Function, AcceptedArgKind};

#[derive(Debug, Default)]
pub struct Base64 {}
SteveL-MSFT marked this conversation as resolved.
Show resolved Hide resolved

impl Function for Base64 {
fn accepted_arg_types(&self) -> Vec<AcceptedArgKind> {
vec![AcceptedArgKind::String]
}

fn min_args(&self) -> usize {
1
}

fn max_args(&self) -> usize {
1
}

fn invoke(&self, args: &[FunctionArg]) -> Result<FunctionResult, DscError> {
let FunctionArg::String(arg) = args.get(0).unwrap() else {
return Err(DscError::Parser("Invalid argument type".to_string()));
};
Ok(FunctionResult::String(general_purpose::STANDARD.encode(arg)))
}
}

#[cfg(test)]
mod tests {
use crate::parser::Statement;

#[test]
fn strings() {
let mut parser = Statement::new().unwrap();
let result = parser.parse_and_execute("[base64('hello world')]").unwrap();
assert_eq!(result, "aGVsbG8gd29ybGQ=");
}

#[test]
fn numbers() {
let mut parser = Statement::new().unwrap();
let result = parser.parse_and_execute("[base64(123)]");
assert!(result.is_err());
}

#[test]
fn nested() {
let mut parser = Statement::new().unwrap();
let result = parser.parse_and_execute("[base64(base64('hello world'))]").unwrap();
assert_eq!(result, "YUdWc2JHOGdkMjl5YkdRPQ==");
}
}
87 changes: 87 additions & 0 deletions dsc_lib/src/functions/concat.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use crate::DscError;
use crate::functions::{Function, FunctionArg, FunctionResult, AcceptedArgKind};

#[derive(Debug, Default)]
pub struct Concat {}
SteveL-MSFT marked this conversation as resolved.
Show resolved Hide resolved

impl Function for Concat {
fn min_args(&self) -> usize {
2
}

fn max_args(&self) -> usize {
usize::MAX
}

fn accepted_arg_types(&self) -> Vec<AcceptedArgKind> {
vec![AcceptedArgKind::String, AcceptedArgKind::Integer]
}

fn invoke(&self, args: &[FunctionArg]) -> Result<FunctionResult, DscError> {
let mut result = String::new();
for arg in args {
match arg {
FunctionArg::String(value) => {
result.push_str(value);
},
FunctionArg::Integer(value) => {
result.push_str(&value.to_string());
},
_ => {
return Err(DscError::Parser("Invalid argument type".to_string()));
}
}
}
Ok(FunctionResult::String(result))
}
}

#[cfg(test)]
mod tests {
use crate::parser::Statement;

#[test]
fn strings() {
let mut parser = Statement::new().unwrap();
let result = parser.parse_and_execute("[concat('a', 'b')]").unwrap();
assert_eq!(result, "ab");
}

#[test]
fn strings_with_spaces() {
let mut parser = Statement::new().unwrap();
let result = parser.parse_and_execute("[concat('a ', ' ', ' b')]").unwrap();
assert_eq!(result, "a b");
}

#[test]
fn numbers() {
let mut parser = Statement::new().unwrap();
let result = parser.parse_and_execute("[concat(1, 2)]").unwrap();
assert_eq!(result, "12");
}

#[test]
fn string_and_numbers() {
let mut parser = Statement::new().unwrap();
let result = parser.parse_and_execute("[concat('a', 1, 'b', 2)]").unwrap();
assert_eq!(result, "a1b2");
}

#[test]
fn nested() {
let mut parser = Statement::new().unwrap();
let result = parser.parse_and_execute("[concat('a', concat('b', 'c'), 'd')]").unwrap();
assert_eq!(result, "abcd");
}

#[test]
fn invalid_one_parameter() {
let mut parser = Statement::new().unwrap();
let result = parser.parse_and_execute("[concat('a')]");
assert!(result.is_err());
}
}
Loading