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

feat: implement the file functions from the WDL standard library. #254

Merged
merged 33 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
1b84431
feat: implement the `basename` function.
peterhuene Nov 13, 2024
e394d3d
feat: implement the `join_paths` function.
peterhuene Nov 13, 2024
a7d6b2d
fix: make assertions in the stdlib debug mode only.
peterhuene Nov 13, 2024
1751cdd
feat: implement the `glob` function.
peterhuene Nov 13, 2024
ebf02b8
feat: implement the `size` function.
peterhuene Nov 14, 2024
ac39848
fix: fix the `wdl check` command to work with directories.
peterhuene Nov 14, 2024
2a9c743
feat: implement the `stdout` and `stderr` functions.
peterhuene Nov 14, 2024
842ebe7
feat: implement the `read_string` function.
peterhuene Nov 14, 2024
4841276
feat: implement the `read_int` function.
peterhuene Nov 14, 2024
b12691e
feat: implement the `read_float` function.
peterhuene Nov 14, 2024
9da0079
feat: implement the `read_boolean` function.
peterhuene Nov 14, 2024
bd7df64
feat: implement the `read_lines` function.
peterhuene Nov 14, 2024
fca9135
feat: implement the `write_lines` function.
peterhuene Nov 15, 2024
d0f4bc9
feat: implement the `read_tsv` function.
peterhuene Nov 15, 2024
3facfe6
refactor: move each stdlib function into its own module.
peterhuene Nov 15, 2024
05ad29f
feat: implement the `write_tsv` function.
peterhuene Nov 16, 2024
24d7a5f
feat: implement the `read_map` function.
peterhuene Nov 16, 2024
7e86fc4
feat: implement the `write_map` function.
peterhuene Nov 16, 2024
c929b52
feat: implement the `read_json` function.
peterhuene Nov 16, 2024
7503fd4
feat: implement the `write_json` function.
peterhuene Nov 18, 2024
7bb15ff
refactor: replace `Value::from_json` with `deserialize`.
peterhuene Nov 18, 2024
4067afe
feat: implement the `read_object` function.
peterhuene Nov 18, 2024
d5e179a
feat: implement the `read_objects` function.
peterhuene Nov 18, 2024
27ef300
feat: implement the `write_object` function.
peterhuene Nov 18, 2024
8bb97f8
feat: implement the `write_objects` function.
peterhuene Nov 18, 2024
c1e7fe0
chore: fresh gauntlet.
peterhuene Nov 18, 2024
4849c36
fix: attempt to fix unit test path separator difference on Windows.
peterhuene Nov 18, 2024
64fc55c
fix: improve error messages for `read_object` and `read_objects`.
peterhuene Nov 19, 2024
e641d8b
chore: update CHANGELOG.md.
peterhuene Nov 19, 2024
edd7acf
Update wdl-engine/src/stdlib/join_paths.rs
peterhuene Nov 19, 2024
f4e86bb
chore: code review feedback.
peterhuene Nov 19, 2024
d2eac6f
chore: code review feedback.
peterhuene Nov 19, 2024
b1b01b6
chore: code review feedback.
peterhuene Nov 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ dirs = "5.0.1"
faster-hex = "0.9.0"
futures = "0.3.30"
git2 = "0.18.3"
glob = "0.3.1"
id-arena = "2.2.1"
indexmap = { version = "2.2.6", features = ["serde"] }
indicatif = "0.17.8"
Expand Down
1,580 changes: 800 additions & 780 deletions Gauntlet.toml

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions gauntlet/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ pub async fn gauntlet(args: Args) -> Result<()> {
};

report
.register(document_identifier, status, elapsed)
.register(document_identifier, status)
.context("failed to register report status")?;
}

Expand All @@ -330,7 +330,7 @@ pub async fn gauntlet(args: Args) -> Result<()> {
.next_section()
.context("failed to transition to next report section")?;
report
.footer(repository_identifier)
.footer(repository_identifier, elapsed)
.context("failed to write report footer")?;
report
.next_section()
Expand Down
6 changes: 3 additions & 3 deletions gauntlet/src/report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,6 @@ impl<T: std::io::Write> Report<T> {
&mut self,
identifier: document::Identifier,
status: Status,
elapsed: Duration,
) -> std::io::Result<()> {
if self.section != Section::Summary {
panic!(
Expand All @@ -185,7 +184,7 @@ impl<T: std::io::Write> Report<T> {
);
}

writeln!(self.inner, "{status} {identifier} ({elapsed:?})")?;
writeln!(self.inner, "{status} {identifier}")?;
self.results.insert(identifier, status);
self.printed = true;

Expand Down Expand Up @@ -238,6 +237,7 @@ impl<T: std::io::Write> Report<T> {
pub fn footer(
&mut self,
repository_identifier: &repository::Identifier,
elapsed: Duration,
) -> std::io::Result<()> {
if self.section != Section::Footer {
panic!(
Expand Down Expand Up @@ -292,7 +292,7 @@ impl<T: std::io::Write> Report<T> {

writeln!(
self.inner,
" ({:.1}%)",
" ({:.1}%) analyzed in {elapsed:?}",
(passed as f64 / considered as f64) * 100.0
)?;
self.printed = true;
Expand Down
106 changes: 86 additions & 20 deletions wdl-analysis/src/stdlib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1651,14 +1651,22 @@ pub struct StandardLibrary {
types: Types,
/// A map of function name to function definition.
functions: IndexMap<&'static str, Function>,
/// The type for `Array[String]`.
pub(crate) array_string: Type,
/// The type for `Array[Int]`.
pub(crate) array_int: Type,
/// The type for `Map[String, Int]`.
pub(crate) map_string_int: Type,
array_int: Type,
/// The type for `Array[String]`.
array_string: Type,
/// The type for `Array[File]`.
array_file: Type,
/// The type for `Array[Object]`.
array_object: Type,
/// The type for `Array[String]+`.
array_string_non_empty: Type,
/// The type for `Array[Array[String]]`.
array_array_string: Type,
/// The type for `Map[String, String]`.
pub(crate) map_string_string: Type,
map_string_string: Type,
/// The type for `Map[String, Int]`.
map_string_int: Type,
}

impl StandardLibrary {
Expand All @@ -1676,6 +1684,46 @@ impl StandardLibrary {
pub fn functions(&self) -> impl ExactSizeIterator<Item = (&'static str, &Function)> {
self.functions.iter().map(|(n, f)| (*n, f))
}

/// Gets the type for `Array[Int]`.
pub fn array_int_type(&self) -> Type {
self.array_int
}

/// Gets the type for `Array[String]`.
pub fn array_string_type(&self) -> Type {
self.array_string
}

/// Gets the type for `Array[File]`.
pub fn array_file_type(&self) -> Type {
self.array_file
}

/// Gets the type for `Array[Object]`.
pub fn array_object_type(&self) -> Type {
self.array_object
}

/// Gets the type for `Array[String]+`.
pub fn array_string_non_empty_type(&self) -> Type {
self.array_string_non_empty
}

/// Gets the type for `Array[Array[String]]`.
pub fn array_array_string_type(&self) -> Type {
self.array_array_string
}

/// Gets the type for `Map[String, String]`.
pub fn map_string_string_type(&self) -> Type {
self.map_string_string
}

/// Gets the type for `Map[String, Int]`.
pub fn map_string_int_type(&self) -> Type {
self.map_string_int
}
}

/// Represents the WDL standard library.
Expand Down Expand Up @@ -1958,6 +2006,15 @@ pub static STDLIB: LazyLock<StandardLibrary> = LazyLock::new(|| {
.insert(
"size",
PolymorphicFunction::new(vec![
// This overload isn't explicitly in the spec, but it fixes an ambiguity in 1.2
// when passed a literal `None` value.
FunctionSignature::builder()
.min_version(SupportedVersion::V1(V1::Two))
.required(1)
.parameter(Type::None)
.parameter(PrimitiveTypeKind::String)
.ret(PrimitiveTypeKind::Float)
.build(),
FunctionSignature::builder()
.required(1)
.parameter(PrimitiveType::optional(PrimitiveTypeKind::File))
Expand All @@ -1969,6 +2026,7 @@ pub static STDLIB: LazyLock<StandardLibrary> = LazyLock::new(|| {
// `String` overload is required as `String` may coerce to either `File` or
// `Directory`, which is ambiguous.
FunctionSignature::builder()
.min_version(SupportedVersion::V1(V1::Two))
.required(1)
.parameter(PrimitiveType::optional(PrimitiveTypeKind::String))
.parameter(PrimitiveTypeKind::String)
Expand Down Expand Up @@ -2131,11 +2189,13 @@ pub static STDLIB: LazyLock<StandardLibrary> = LazyLock::new(|| {
.ret(array_array_string)
.build(),
FunctionSignature::builder()
.min_version(SupportedVersion::V1(V1::Two))
.parameter(PrimitiveTypeKind::File)
.parameter(PrimitiveTypeKind::Boolean)
.ret(array_object)
.build(),
FunctionSignature::builder()
.min_version(SupportedVersion::V1(V1::Two))
.parameter(PrimitiveTypeKind::File)
.parameter(PrimitiveTypeKind::Boolean)
.parameter(array_string)
Expand All @@ -2158,18 +2218,16 @@ pub static STDLIB: LazyLock<StandardLibrary> = LazyLock::new(|| {
.ret(PrimitiveTypeKind::File)
.build(),
FunctionSignature::builder()
.type_parameter("S", StructConstraint)
.parameter(GenericArrayType::new(GenericType::Parameter("S")))
.ret(PrimitiveTypeKind::File)
.build(),
FunctionSignature::builder()
.min_version(SupportedVersion::V1(V1::Two))
.parameter(array_array_string)
.parameter(PrimitiveTypeKind::Boolean)
.parameter(array_string)
.ret(PrimitiveTypeKind::File)
.build(),
FunctionSignature::builder()
.type_parameter("S", StructConstraint)
.min_version(SupportedVersion::V1(V1::Two))
.type_parameter("S", PrimitiveStructConstraint)
.required(1)
.parameter(GenericArrayType::new(GenericType::Parameter("S")))
.parameter(PrimitiveTypeKind::Boolean)
.parameter(array_string)
Expand Down Expand Up @@ -2289,7 +2347,8 @@ pub static STDLIB: LazyLock<StandardLibrary> = LazyLock::new(|| {
.ret(PrimitiveTypeKind::File)
.build(),
FunctionSignature::builder()
.type_parameter("S", StructConstraint)
.min_version(SupportedVersion::V1(V1::One))
.type_parameter("S", PrimitiveStructConstraint)
.parameter(GenericType::Parameter("S"))
.ret(PrimitiveTypeKind::File)
.build(),
Expand All @@ -2310,7 +2369,8 @@ pub static STDLIB: LazyLock<StandardLibrary> = LazyLock::new(|| {
.ret(PrimitiveTypeKind::File)
.build(),
FunctionSignature::builder()
.type_parameter("S", StructConstraint)
.min_version(SupportedVersion::V1(V1::One))
.type_parameter("S", PrimitiveStructConstraint)
.parameter(GenericArrayType::new(GenericType::Parameter("S")))
.ret(PrimitiveTypeKind::File)
.build(),
Expand Down Expand Up @@ -2853,10 +2913,14 @@ pub static STDLIB: LazyLock<StandardLibrary> = LazyLock::new(|| {
StandardLibrary {
types,
functions,
array_string,
array_int,
map_string_int,
array_string,
array_file,
array_object,
array_string_non_empty,
array_array_string,
map_string_string,
map_string_int,
}
});

Expand Down Expand Up @@ -2912,6 +2976,7 @@ mod test {
"join_paths(File, Array[String]+) -> File",
"join_paths(Array[String]+) -> File",
"glob(String) -> Array[File]",
"size(None, <String>) -> Float",
"size(File?, <String>) -> Float",
"size(String?, <String>) -> Float",
"size(Directory?, <String>) -> Float",
Expand All @@ -2929,19 +2994,20 @@ mod test {
"read_tsv(File, Boolean) -> Array[Object]",
"read_tsv(File, Boolean, Array[String]) -> Array[Object]",
"write_tsv(Array[Array[String]]) -> File",
"write_tsv(Array[S]) -> File where `S`: any structure",
"write_tsv(Array[Array[String]], Boolean, Array[String]) -> File",
"write_tsv(Array[S], Boolean, Array[String]) -> File where `S`: any structure",
"write_tsv(Array[S], <Boolean>, <Array[String]>) -> File where `S`: any structure \
containing only primitive types",
"read_map(File) -> Map[String, String]",
"write_map(Map[String, String]) -> File",
"read_json(File) -> Union",
"write_json(X) -> File where `X`: any JSON-serializable type",
"read_object(File) -> Object",
"read_objects(File) -> Array[Object]",
"write_object(Object) -> File",
"write_object(S) -> File where `S`: any structure",
"write_object(S) -> File where `S`: any structure containing only primitive types",
"write_objects(Array[Object]) -> File",
"write_objects(Array[S]) -> File where `S`: any structure",
"write_objects(Array[S]) -> File where `S`: any structure containing only primitive \
types",
"prefix(String, Array[P]) -> Array[String] where `P`: any required primitive type",
"suffix(String, Array[P]) -> Array[String] where `P`: any required primitive type",
"quote(Array[P]) -> Array[String] where `P`: any required primitive type",
Expand Down
26 changes: 24 additions & 2 deletions wdl-analysis/src/stdlib/constraints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use std::fmt;

use crate::types::Coercible;
use crate::types::CompoundType;
use crate::types::CompoundTypeDef;
use crate::types::Optional;
Expand Down Expand Up @@ -114,6 +115,27 @@ impl Constraint for StructConstraint {
}
}

/// Represents a constraint that ensures the type is any structure that contains
/// only primitive types.
#[derive(Debug, Copy, Clone)]
pub struct PrimitiveStructConstraint;

impl Constraint for PrimitiveStructConstraint {
fn description(&self) -> &'static str {
"any structure containing only primitive types"
}

fn satisfied(&self, types: &Types, ty: Type) -> bool {
if let Type::Compound(ty) = ty {
if let CompoundTypeDef::Struct(s) = types.type_definition(ty.definition()) {
return s.members().values().all(|ty| ty.as_primitive().is_some());
}
}

false
}
}

/// Represents a constraint that ensures the type is JSON serializable.
#[derive(Debug, Copy, Clone)]
pub struct JsonSerializableConstraint;
Expand All @@ -130,8 +152,8 @@ impl Constraint for JsonSerializableConstraint {
CompoundTypeDef::Array(ty) => type_is_serializable(types, ty.element_type()),
CompoundTypeDef::Pair(_) => false,
CompoundTypeDef::Map(ty) => {
!ty.key_type().is_optional()
&& matches!(ty.key_type(), Type::Primitive(ty) if ty.kind() == PrimitiveTypeKind::String)
ty.key_type()
.is_coercible_to(types, &PrimitiveTypeKind::String.into())
&& type_is_serializable(types, ty.value_type())
}
CompoundTypeDef::Struct(s) => s
Expand Down
Loading