diff --git a/CHANGELOG.md b/CHANGELOG.md index f855479..b2be52d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Added - Added negated equality assertions for comparative and length. (#40) +- Added coercion for versions, which takes a number or array or string and turns it into a version (niave approach, may need some further work) (#11) ## Changed diff --git a/src/base-type.typ b/src/base-type.typ index f7d55e0..68f3dea 100644 --- a/src/base-type.typ +++ b/src/base-type.typ @@ -22,7 +22,11 @@ return ( valkyrie-type: true, name: name, - description: if (description != none){ description } else { name }, + description: if (description != none) { + description + } else { + name + }, optional: optional, default: default, types: types, diff --git a/src/coercions.typ b/src/coercions.typ index ee16f06..4521942 100644 --- a/src/coercions.typ +++ b/src/coercions.typ @@ -1,109 +1,5 @@ - -/// If the tested value is not already of dictionary type, the function provided as argument is expected to return a dictionary type with a shape that passes validation. -/// -/// #example[``` -/// #let schema = z.dictionary( -/// pre-transform: z.coerce.dictionary((it)=>(name: it)), -/// (name: z.string()) -/// ) -/// -/// #z.parse("Hello", schema) \ -/// #z.parse((name: "Hello"), schema) -/// ```] -/// -/// - fn (function): Transformation function that the tested value and returns a dictionary that has a shape that passes validation. -#let dictionary(fn) = (self, it) => { - if (type(it) != type((:))) { - return fn(it) - } - it -} - -/// If the tested value is not already of array type, it is transformed into an array of size 1 -/// -/// #example[``` -/// #let schema = z.array( -/// pre-transform: z.coerce.array, -/// z.string() -/// ) -/// -/// #z.parse("Hello", schema) \ -/// #z.parse(("Hello", "world"), schema) -/// ```] -#let array(self, it) = { - if (type(it) != type(())) { - return (it,) - } - it -} - -/// Tested value is forceably converted to content type -/// -/// #example[``` -/// #let schema = z.content( -/// pre-transform: z.coerce.content -/// ) -/// -/// #type(z.parse("Hello", schema)) \ -/// #type(z.parse(123456, schema)) -/// ```] -#let content(self, it) = [#it] - -/// An attempt is made to convert string, numeric, or dictionary inputs into datetime objects -/// -/// #example[``` -/// #let schema = z.date( -/// pre-transform: z.coerce.date -/// ) -/// -/// #z.parse(2020, schema) \ -/// #z.parse("2020-03-15", schema) \ -/// #z.parse("2020/03/15", schema) \ -/// #z.parse((year: 2020, month: 3, day: 15), schema) \ -/// ```] -#let date(self, it) = { - if (type(it) == type(datetime.today())) { - return it - } - if (type(it) == int) { - // assume this is the year - assert( - it > 1000 and it < 3000, - message: "The date is assumed to be a year between 1000 and 3000", - ) - return datetime(year: it, month: 1, day: 1) - } - - if (type(it) == str) { - let yearMatch = it.find(regex(`^([1|2])([0-9]{3})$`.text)) - if (yearMatch != none) { - // This isn't awesome, but probably fine - return datetime(year: int(it), month: 1, day: 1) - } - let dateMatch = it.find( - regex(`^([1|2])([0-9]{3})([-\/])([0-9]{1,2})([-\/])([0-9]{1,2})$`.text), - ) - if (dateMatch != none) { - let parts = it.split(regex("[-\/]")) - return datetime( - year: int(parts.at(0)), - month: int(parts.at(1)), - day: int(parts.at(2)), - ) - } - panic("Unknown datetime object from string, try: `2020/03/15` as YYYY/MM/DD, also accepts `2020-03-15`") - } - - if (type(it) == type((:))) { - if ("year" in it) { - return return datetime( - year: it.at("year"), - month: it.at("month", default: 1), - day: it.at("day", default: 1), - ) - } - panic("Unknown datetime object from dictionary, try: `(year: 2022, month: 2, day: 3)`") - } - panic("Unknown date of type '" + type(it) + "' accepts: datetime, str, int, and object") - -} \ No newline at end of file +#import "coercions/dictionary.typ": dictionary +#import "coercions/array.typ": array +#import "coercions/content.typ": content +#import "coercions/date.typ": date +#import "coercions/version.typ": version diff --git a/src/coercions/array.typ b/src/coercions/array.typ new file mode 100644 index 0000000..948e8e9 --- /dev/null +++ b/src/coercions/array.typ @@ -0,0 +1,17 @@ +/// If the tested value is not already of array type, it is transformed into an array of size 1 +/// +/// #example[``` +/// #let schema = z.array( +/// pre-transform: z.coerce.array, +/// z.string() +/// ) +/// +/// #z.parse("Hello", schema) \ +/// #z.parse(("Hello", "world"), schema) +/// ```] +#let array(self, it) = { + if (type(it) != type(())) { + return (it,) + } + it +} \ No newline at end of file diff --git a/src/coercions/content.typ b/src/coercions/content.typ new file mode 100644 index 0000000..7e8e63d --- /dev/null +++ b/src/coercions/content.typ @@ -0,0 +1,12 @@ + +/// Tested value is forceably converted to content type +/// +/// #example[``` +/// #let schema = z.content( +/// pre-transform: z.coerce.content +/// ) +/// +/// #type(z.parse("Hello", schema)) \ +/// #type(z.parse(123456, schema)) +/// ```] +#let content(self, it) = [#it] \ No newline at end of file diff --git a/src/coercions/date.typ b/src/coercions/date.typ new file mode 100644 index 0000000..90dbb17 --- /dev/null +++ b/src/coercions/date.typ @@ -0,0 +1,58 @@ +/// An attempt is made to convert string, numeric, or dictionary inputs into datetime objects +/// +/// #example[``` +/// #let schema = z.date( +/// pre-transform: z.coerce.date +/// ) +/// +/// #z.parse(2020, schema) \ +/// #z.parse("2020-03-15", schema) \ +/// #z.parse("2020/03/15", schema) \ +/// #z.parse((year: 2020, month: 3, day: 15), schema) \ +/// ```] +#let date(self, it) = { + if (type(it) == datetime) { + return it + } + if (type(it) == int) { + // assume this is the year + assert( + it > 1000 and it < 3000, + message: "The date is assumed to be a year between 1000 and 3000", + ) + return datetime(year: it, month: 1, day: 1) + } + + if (type(it) == str) { + let yearMatch = it.find(regex(`^([1|2])([0-9]{3})$`.text)) + if (yearMatch != none) { + // This isn't awesome, but probably fine + return datetime(year: int(it), month: 1, day: 1) + } + let dateMatch = it.find( + regex(`^([1|2])([0-9]{3})([-\/])([0-9]{1,2})([-\/])([0-9]{1,2})$`.text), + ) + if (dateMatch != none) { + let parts = it.split(regex("[-\/]")) + return datetime( + year: int(parts.at(0)), + month: int(parts.at(1)), + day: int(parts.at(2)), + ) + } + panic("Unknown datetime object from string, try: `2020/03/15` as YYYY/MM/DD, also accepts `2020-03-15`") + } + + if (type(it) == type((:))) { + if ("year" in it) { + return return datetime( + year: it.at("year"), + month: it.at("month", default: 1), + day: it.at("day", default: 1), + ) + } + panic("Unknown datetime object from dictionary, try: `(year: 2022, month: 2, day: 3)`") + } + panic("Unknown date of type '" + type(it) + "' accepts: datetime, str, int, and object") + +} \ No newline at end of file diff --git a/src/coercions/dictionary.typ b/src/coercions/dictionary.typ new file mode 100644 index 0000000..a27f107 --- /dev/null +++ b/src/coercions/dictionary.typ @@ -0,0 +1,20 @@ + +/// If the tested value is not already of dictionary type, the function provided as argument is expected to return a dictionary type with a shape that passes validation. +/// +/// #example[``` +/// #let schema = z.dictionary( +/// pre-transform: z.coerce.dictionary((it)=>(name: it)), +/// (name: z.string()) +/// ) +/// +/// #z.parse("Hello", schema) \ +/// #z.parse((name: "Hello"), schema) +/// ```] +/// +/// - fn (function): Transformation function that the tested value and returns a dictionary that has a shape that passes validation. +#let dictionary(fn) = (self, it) => { + if (type(it) != type((:))) { + return fn(it) + } + it +} \ No newline at end of file diff --git a/src/coercions/version.typ b/src/coercions/version.typ new file mode 100644 index 0000000..be450ad --- /dev/null +++ b/src/coercions/version.typ @@ -0,0 +1,25 @@ +#let stdversion = version + +/// Tested value is forceably converted to version type +/// +/// #example[``` +/// #let schema = z.version( +/// pre-transform: z.coerce.version +/// ) +/// +/// #type(z.parse("0.1.1", schema)) \ +/// #type(z.parse(1, schema)) +/// #type(z.parse((1,1,), schema)) +/// ```] +#let version(self, it) = { + if type(it) == str { + return stdversion( + it.split(".").filter(it => it != "").map(int), + ) + } else if type(it) == int { + return stdversion(it) + } else if type(it) == array { + return stdversion(it.map(int)) + } + it +} \ No newline at end of file diff --git a/src/types/dictionary.typ b/src/types/dictionary.typ index d3ea6ac..23edbc9 100644 --- a/src/types/dictionary.typ +++ b/src/types/dictionary.typ @@ -34,7 +34,7 @@ } return it }, - ..args.named() + ..args.named(), ) + ( dictionary-schema: dictionary-schema, handle-descendents: (self, it, ctx: z-ctx(), scope: ()) => { diff --git a/src/types/logical.typ b/src/types/logical.typ index cda7a75..bbf522b 100644 --- a/src/types/logical.typ +++ b/src/types/logical.typ @@ -18,7 +18,10 @@ base-type( name: "either", - description: "[" + args.pos().map(it => it.name).join(", ", last: " or ") + "]", + description: "[" + args.pos().map(it => it.name).join( + ", ", + last: " or ", + ) + "]", ..args.named(), ) + ( strict: strict, diff --git a/src/types/tuple.typ b/src/types/tuple.typ index 151c3f7..ff8e828 100644 --- a/src/types/tuple.typ +++ b/src/types/tuple.typ @@ -22,9 +22,15 @@ tuple-exact: exact, tuple-schema: args.pos(), handle-descendents: (self, it, ctx: z-ctx(), scope: ()) => { - if (self.tuple-exact and self.tuple-schema.len() != it.len()){ - (self.fail-validation)(self, it, ctx: ctx, scope: scope, - message: "Expected " + str(self.tuple-schema.len()) + " values, but got " + str(it.len()) + if (self.tuple-exact and self.tuple-schema.len() != it.len()) { + (self.fail-validation)( + self, + it, + ctx: ctx, + scope: scope, + message: "Expected " + str( + self.tuple-schema.len(), + ) + " values, but got " + str(it.len()), ) } for (key, schema) in self.tuple-schema.enumerate() { diff --git a/tests/coercions/version/ref/1.png b/tests/coercions/version/ref/1.png new file mode 100644 index 0000000..533bc0b Binary files /dev/null and b/tests/coercions/version/ref/1.png differ diff --git a/tests/coercions/version/test.typ b/tests/coercions/version/test.typ new file mode 100644 index 0000000..e430662 --- /dev/null +++ b/tests/coercions/version/test.typ @@ -0,0 +1,13 @@ +#import "/src/lib.typ" as z +#set page(height: auto, width: auto) + +#let test(x) = z.parse(x, z.version(pre-transform: z.coerce.version)) + +#repr(test(1)) + +#repr(test((1, 1))) + +#repr(test("1.1.")) + +#repr(test("1.0.1.0")) + diff --git a/tests/types/tuple/test.typ b/tests/types/tuple/test.typ index 0bfa3d5..0414e5b 100644 --- a/tests/types/tuple/test.typ +++ b/tests/types/tuple/test.typ @@ -5,8 +5,8 @@ let test-tuple = ( "123", "email@address.co.uk", - 1.1 - ) + 1.1, + ) z.parse( test-tuple,