eval, lex, parse commands (#30)

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
Anand Krishnamoorthi
2023-10-30 21:45:43 -07:00
committed by GitHub
parent 63ecc44a48
commit 7a3d5e7e02
7 changed files with 142 additions and 262 deletions

View File

@@ -2,34 +2,20 @@
// Licensed under the MIT License.
use anyhow::{bail, Context, Result};
use clap::Parser;
use clap::{Parser, Subcommand};
#[derive(clap::Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
/// Policy or data files. Rego, json or yaml.
#[arg(required(true), long, short, value_name = "policy.rego")]
data: Vec<String>,
/// Input file. json or yaml.
#[arg(long, short, value_name = "input.rego")]
fn rego_eval(
files: &[String],
input: Option<String>,
// Query. Rego expression.
#[arg(long, short)]
query: Option<String>,
}
fn main() -> Result<()> {
let cli = Cli::parse();
let enable_tracing = false;
enable_tracing: bool,
) -> Result<()> {
// User specified data.
let mut data = regorus::Value::new_object();
// Read all policy files.
let mut policies = vec![];
for file in cli.data.iter() {
for file in files.iter() {
let contents =
std::fs::read_to_string(file).with_context(|| format!("Failed to read {file}"))?;
@@ -54,7 +40,7 @@ fn main() -> Result<()> {
let mut sources = vec![];
for (idx, rego) in policies.iter().enumerate() {
sources.push(regorus::Source {
file: &cli.data[idx],
file: &files[idx],
contents: rego.as_str(),
lines: rego.split('\n').collect(),
});
@@ -67,6 +53,22 @@ fn main() -> Result<()> {
modules.push(parser.parse()?);
}
// Parse input file.
let input = if let Some(file) = input {
let input_contents = std::fs::read_to_string(file.clone())
.with_context(|| format!("Failed to read {file}"))?;
Some(if file.ends_with(".json") {
serde_json::from_str(&input_contents)?
} else if file.ends_with(".yaml") {
serde_yaml::from_str(&input_contents)?
} else {
bail!("invalid input file {file}");
})
} else {
None
};
// Analyze the modules and determine how statements must be schedules.
let analyzer = regorus::Analyzer::new();
let schedule = analyzer.analyze(&modules)?;
@@ -76,13 +78,13 @@ fn main() -> Result<()> {
let mut interpreter = regorus::Interpreter::new(modules_ref)?;
// Prepare for evalution.
interpreter.prepare_for_eval(Some(&schedule), &Some(data))?;
interpreter.prepare_for_eval(Some(&schedule), &Some(data.clone()))?;
// Evaluate all the modules.
interpreter.eval(&None, &None, false, Some(&schedule))?;
interpreter.eval(&Some(data), &input, false, Some(&schedule))?;
// Fetch query string. If none specified, use "data".
let query = match &cli.query {
let query = match &query {
Some(query) => query,
_ => "data",
};
@@ -109,3 +111,118 @@ fn main() -> Result<()> {
Ok(())
}
fn rego_lex(file: String, verbose: bool) -> Result<()> {
let contents =
std::fs::read_to_string(file.clone()).with_context(|| format!("Failed to read {file}"))?;
// Create source.
let source = regorus::Source {
file: file.as_str(),
contents: contents.as_str(),
lines: contents.split('\n').collect(),
};
// Create lexer.
let mut lexer = regorus::Lexer::new(&source);
// Read tokens until EOF.
loop {
let token = lexer.next_token()?;
if token.0 == regorus::TokenKind::Eof {
break;
}
if verbose {
// Print each token's line and mark with with ^.
println!("{}", token.1.message("", ""));
}
// Print the token.
println!("{token:?}");
}
Ok(())
}
fn rego_parse(file: String) -> Result<()> {
let contents =
std::fs::read_to_string(file.clone()).with_context(|| format!("Failed to read {file}"))?;
// Create source.
let source = regorus::Source {
file: file.as_str(),
contents: contents.as_str(),
lines: contents.split('\n').collect(),
};
// Create a parser and parse the source.
let mut parser = regorus::Parser::new(&source)?;
let ast = parser.parse()?;
println!("{ast:#?}");
Ok(())
}
#[derive(Subcommand)]
enum RegorusCommand {
/// Evaluate a Rego Query.
Eval {
/// Policy or data files. Rego, json or yaml.
#[arg(
required(true),
long,
short,
value_name = "policy.rego|data.json|data.yaml"
)]
data: Vec<String>,
/// Input file. json or yaml.
#[arg(long, short, value_name = "input.rego")]
input: Option<String>,
/// Query. Rego query block.
query: Option<String>,
/// Enable tracing.
#[arg(long, short)]
trace: bool,
},
/// Tokenize a Rego policy.
Lex {
/// Rego policy file.
file: String,
/// Verbose output.
#[arg(long, short)]
verbose: bool,
},
/// Parse q Rego policy.
Parse {
/// Rego policy file.
file: String,
},
}
#[derive(clap::Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
#[command(subcommand)]
command: RegorusCommand,
}
fn main() -> Result<()> {
// Parse and dispatch command.
let cli = Cli::parse();
match cli.command {
RegorusCommand::Eval {
data,
input,
query,
trace,
} => rego_eval(&data, input, query, trace),
RegorusCommand::Lex { file, verbose } => rego_lex(file, verbose),
RegorusCommand::Parse { file } => rego_parse(file),
}
}

View File

@@ -1,13 +0,0 @@
#!/bin/bash
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
set -e
rego=$(realpath -e $1)
if [ ! -z "$2" ]; then
input=$(realpath -e $2)
cargo test interpreter::one_file -- --include-ignored --nocapture "$rego" "$input"
else
cargo test interpreter::one_file -- --include-ignored --nocapture "$rego"
fi

View File

@@ -1,27 +0,0 @@
#!/bin/bash
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
set -e
usage="usage: rego-lex <policy.rego> [-v]"
if [ -z "$1" ]; then
echo "$usage"
exit 1
fi
rego=$(realpath -e $1)
case "$2" in
"-v")
verbose="verbose"
;;
*)
if [ ! -z "$2" ]; then
echo "$usage"
exit 1
fi
esac
eval "cargo test lexer::one_file -- --include-ignored --nocapture $rego $verbose"

View File

@@ -1,8 +0,0 @@
#!/bin/bash
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
set -e
rego=$(realpath -e $1)
cargo test parser::one_file -- --include-ignored --nocapture "$rego"

View File

@@ -380,53 +380,6 @@ pub fn eval_file(
Ok(results)
}
#[test]
#[ignore = "intended for use by scripts/rego-eval"]
fn one_file() -> Result<()> {
env_logger::init();
let mut file = String::default();
let mut input = None;
for a in env::args() {
if a.ends_with(".rego") {
file = a;
} else if a.ends_with(".json") {
let input_json = std::fs::read_to_string(&a)?;
let value = Value::from_json_str(input_json.as_str())?;
input = Some(value);
}
}
if file.is_empty() {
bail!("missing <policy.rego>");
}
let contents = std::fs::read_to_string(&file)?;
let source = Source {
file: file.as_str(),
contents: contents.as_str(),
lines: contents.split('\n').collect(),
};
let mut parser = Parser::new(&source)?;
let modules = vec![parser.parse()?];
let analyzer = Analyzer::new();
let schedule = analyzer.analyze(&modules)?;
let mut modules_ref = vec![];
for m in &modules {
modules_ref.push(m);
}
let mut interpreter = interpreter::Interpreter::new(modules_ref)?;
interpreter.prepare_for_eval(Some(&schedule), &None)?;
let results = interpreter.eval_modules(&input, true)?;
println!("eval results:\n{}", serde_json::to_string_pretty(&results)?);
Ok(())
}
#[derive(PartialEq, Debug)]
pub enum ValueOrVec {
Single(Value),
@@ -526,7 +479,6 @@ fn yaml_test_impl(file: &str, is_opa_test: bool) -> Result<()> {
// Convert value to json compatible representation.
let results =
Value::from_json_str(serde_json::to_string(&results)?.as_str())?;
dbg!((&results, &expected_results[0]));
match_values(&results, &expected_results[0])?;
} else {
check_output(&results, &expected_results)?;

View File

@@ -1,14 +1,11 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#![cfg(test)]
use anyhow::{bail, Result};
use regorus::*;
use serde::{Deserialize, Serialize};
use std::env;
use test_generator::test_resources;
//use walkdir::WalkDir;
fn get_tokens<'source>(source: &'source Source<'source>) -> Result<Vec<Token<'source>>> {
let mut tokens = vec![];
@@ -58,46 +55,6 @@ fn check_loc(tok: &Token) -> Result<()> {
}
}
#[test]
#[ignore = "intended for use by scripts/lex-file"]
fn one_file() -> Result<()> {
let mut file = String::default();
let mut verbose = false;
for a in env::args() {
if a.ends_with(".rego") {
file = a.clone();
}
if matches!(a.as_str(), "verbose") {
verbose = true;
}
}
if file.is_empty() {
bail!("missing <policy.rego>")
}
let contents = std::fs::read_to_string(&file)?;
let source = Source {
file: file.as_str(),
contents: contents.as_str(),
lines: contents.split('\n').collect(),
};
for tok in &get_tokens(&source)? {
if tok.0 == TokenKind::Eof {
break;
}
check_loc(tok)?;
if verbose {
println!("{}", tok.1.source.message(tok.1.line, tok.1.col, "", ""));
}
println!("{tok:?}");
}
Ok(())
}
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Case {
pub rego: String,
@@ -219,36 +176,6 @@ fn one_yaml() -> Result<()> {
yaml_test(file.as_str())
}
/*
fn run_yaml_tests_in(folder: &str) -> Result<()> {
let mut total = 0;
for entry in WalkDir::new(folder)
.follow_links(true)
.into_iter()
.filter_map(|e| e.ok())
{
let path = entry
.path()
.to_str()
.ok_or_else(|| anyhow!("failed to convert path to utf8 {:?}", entry.path()))?;
if !path.ends_with(".yaml") {
continue;
}
total += 1;
yaml_test(path)?;
}
println!("{} lexer yaml tests passed.", total);
Ok(())
}
#[test]
fn lexer_yaml_tests() -> Result<()> {
run_yaml_tests_in("tests/lexer")
}*/
#[test_resources("tests/lexer/**/*.yaml")]
fn run(path: &str) {
yaml_test(path).unwrap()

View File

@@ -1,14 +1,11 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#![cfg(test)]
use anyhow::{anyhow, bail, Result};
use regorus::*;
use serde::{Deserialize, Serialize};
use std::env;
use test_generator::test_resources;
//use walkdir::WalkDir;
macro_rules! my_assert_eq {
($left:expr, $right:expr, $($arg:tt)+) => {
@@ -23,34 +20,6 @@ macro_rules! my_assert_eq {
}
}
#[test]
#[ignore = "intended for use by scripts/rego-parse"]
fn one_file() -> Result<()> {
let mut file = String::default();
for a in env::args() {
if a.ends_with(".rego") {
file = a;
break;
}
}
if file.is_empty() {
bail!("missing <policy.rego>");
}
let contents = std::fs::read_to_string(&file)?;
let source = Source {
file: file.as_str(),
contents: contents.as_str(),
lines: contents.split('\n').collect(),
};
let mut parser = Parser::new(&source)?;
let ast = parser.parse()?;
println!("{ast:#?}");
Ok(())
}
fn skip_value(v: &Value) -> bool {
matches!(v, Value::String(s) if s == "--skip--")
}
@@ -747,49 +716,12 @@ fn one_yaml() -> Result<()> {
}
if file.is_empty() {
bail!("missing <policy.rego>");
bail!("missing yaml test file");
}
yaml_test(file.as_str())
}
/*
fn run_yaml_tests_in(folder: &str) -> Result<()> {
let mut total = 0;
for entry in WalkDir::new(folder)
.follow_links(true)
.into_iter()
.filter_map(|e| e.ok())
{
let path = entry
.path()
.to_str()
.ok_or_else(|| anyhow!("failed to convert path to utf8 {:?}", entry.path()))?;
if !path.ends_with(".yaml") {
continue;
}
total += 1;
match yaml_test(path) {
Ok(_) => (),
Err(e) => {
bail!("test failed.");
}
}
}
println!("{} parser yaml tests passed.", total);
Ok(())
}
#[test]
fn parser_yaml_tests() -> Result<()> {
run_yaml_tests_in("tests/parser")
}
*/
#[test_resources("tests/parser/**/*.yaml")]
fn run(path: &str) {
yaml_test(path).unwrap()