diff --git a/js-rattler/crate/lib.rs b/js-rattler/crate/lib.rs index a19461337..bf673386f 100644 --- a/js-rattler/crate/lib.rs +++ b/js-rattler/crate/lib.rs @@ -1,12 +1,12 @@ mod error; mod gateway; +mod parse_strictness; pub mod solve; mod utils; mod version; mod version_spec; pub use error::{JsError, JsResult}; -use rattler_conda_types::ParseStrictness; use wasm_bindgen::prelude::*; @@ -21,29 +21,3 @@ static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; pub fn start() { utils::set_panic_hook(); } - -/// Defines how strict a parser should be when parsing an object from a string. -/// @public -#[wasm_bindgen(js_name=ParseStrictness)] -pub enum JsParseStrictness { - Strict, - Lenient, -} - -impl From for JsParseStrictness { - fn from(value: ParseStrictness) -> Self { - match value { - ParseStrictness::Strict => JsParseStrictness::Strict, - ParseStrictness::Lenient => JsParseStrictness::Lenient, - } - } -} - -impl From for ParseStrictness { - fn from(value: JsParseStrictness) -> Self { - match value { - JsParseStrictness::Strict => ParseStrictness::Strict, - JsParseStrictness::Lenient => ParseStrictness::Lenient, - } - } -} diff --git a/js-rattler/crate/parse_strictness.rs b/js-rattler/crate/parse_strictness.rs new file mode 100644 index 000000000..f5a33a316 --- /dev/null +++ b/js-rattler/crate/parse_strictness.rs @@ -0,0 +1,37 @@ +use rattler_conda_types::ParseStrictness; +use serde::Deserialize; +use wasm_bindgen::prelude::wasm_bindgen; + +#[wasm_bindgen(typescript_custom_section)] +const PARSE_STRICTNESS_TS: &'static str = r#" +/** + * Defines how strict a parser should be when parsing an object from a string. + * + * @public + */ +export type ParseStrictness = "strict" | "lenient"; +"#; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(js_name = "ParseStrictness", typescript_type = "ParseStrictness")] + pub type JsParseStrictness; +} + +impl TryFrom for ParseStrictness { + type Error = serde_wasm_bindgen::Error; + + fn try_from(value: JsParseStrictness) -> Result { + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub enum EnumNames { + Strict, + Lenient, + } + + match serde_wasm_bindgen::from_value(value.obj)? { + EnumNames::Strict => Ok(ParseStrictness::Strict), + EnumNames::Lenient => Ok(ParseStrictness::Lenient), + } + } +} diff --git a/js-rattler/crate/version.rs b/js-rattler/crate/version.rs index 4fa4f4f47..7347dda21 100644 --- a/js-rattler/crate/version.rs +++ b/js-rattler/crate/version.rs @@ -1,9 +1,20 @@ -use crate::JsResult; use rattler_conda_types::{Version, VersionBumpType}; use std::{cmp::Ordering, str::FromStr}; use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::JsValue; + +use crate::JsResult; -#[wasm_bindgen] +/// This class implements an order relation between version strings. Version +/// strings can contain the usual alphanumeric characters (A-Za-z0-9), separated +/// into segments by dots and underscores. Empty segments (i.e. two consecutive +/// dots, a leading/trailing underscore) are not permitted. An optional epoch +/// number - an integer followed by '!' - can precede the actual version string +/// (this is useful to indicate a change in the versioning scheme itself). +/// Version comparison is case-insensitive. +/// +/// @public +#[wasm_bindgen(js_name = "Version")] #[repr(transparent)] #[derive(Eq, PartialEq)] pub struct JsVersion { @@ -28,110 +39,198 @@ impl AsRef for JsVersion { } } -#[wasm_bindgen] -pub struct MajorMinor(pub usize, pub usize); - -#[wasm_bindgen] +#[wasm_bindgen(js_class = "Version")] impl JsVersion { + /// Constructs a new Version object from a string representation. #[wasm_bindgen(constructor)] - pub fn new(version: &str) -> JsResult { + pub fn new( + #[wasm_bindgen(param_description = "The string representation of the version.")] + version: &str, + ) -> JsResult { let version = Version::from_str(version)?; Ok(version.into()) } + /// Returns the string representation of the version. + /// + /// An attempt is made to return the version in the same format as the input + /// string but this is not guaranteed. + #[wasm_bindgen(js_name = "toString")] pub fn as_str(&self) -> String { format!("{}", self.as_ref()) } + /// The epoch part of the version. E.g. `1` in `1!2.3`. #[wasm_bindgen(getter)] pub fn epoch(&self) -> Option { self.as_ref().epoch_opt().map(|v| v as usize) } - #[wasm_bindgen(getter)] + /// `true` if the version has a local part. E.g. `2.3` in `1+2.3`. + #[wasm_bindgen(getter, js_name = "hasLocal")] pub fn has_local(&self) -> bool { self.as_ref().has_local() } - #[wasm_bindgen(getter)] + /// `true` if the version is considered a development version. + /// + /// A development version is a version that contains the string `dev` in the + /// version string. + #[wasm_bindgen(getter, js_name = "isDev")] pub fn is_dev(&self) -> bool { self.as_ref().is_dev() } - pub fn as_major_minor(&self) -> Option { + /// Returns the major and minor part of the version if the version does not + /// represent a typical major minor version. If any of the parts are not a + /// single number, undefined is returned. + // TODO: Simplify when https://github.com/rustwasm/wasm-bindgen/issues/122 is fixed + #[wasm_bindgen( + js_name = "asMajorMinor", + unchecked_return_type = "[number, number] | undefined" + )] + pub fn as_major_minor(&self) -> Option> { let (major, minor) = self.as_ref().as_major_minor()?; - Some(MajorMinor(major as _, minor as _)) - } - - pub fn starts_with(&self, other: &Self) -> bool { + Some(vec![ + JsValue::from(major as usize), + JsValue::from(minor as usize), + ]) + } + + /// Returns true if this version starts with the other version. This is + /// defined as the other version being a prefix of this version. + #[wasm_bindgen(js_name = "startsWith")] + pub fn starts_with( + &self, + #[wasm_bindgen(param_description = "The version to use for the comparison")] other: &Self, + ) -> bool { self.as_ref().starts_with(other.as_ref()) } - pub fn compatible_with(&self, other: &Self) -> bool { + /// Returns true if this version is compatible with the other version. + #[wasm_bindgen(js_name = "compatibleWith")] + pub fn compatible_with( + &self, + #[wasm_bindgen(param_description = "The version to use for the comparison")] other: &Self, + ) -> bool { self.as_ref().compatible_with(other.as_ref()) } - pub fn pop_segments(&self, n: usize) -> Option { + /// Pop the last `n` segments from the version. + #[wasm_bindgen(js_name = "popSegments")] + pub fn pop_segments( + &self, + #[wasm_bindgen(param_description = "The number of segments to pop")] n: usize, + ) -> Option { Some(self.as_ref().pop_segments(n)?.into()) } - pub fn extend_to_length(&self, length: usize) -> JsResult { + /// Extend the version to the given length by adding zeros. If the version + /// is already at the specified length or longer the original version + /// will be returned. + #[wasm_bindgen(js_name = "extendToLength")] + pub fn extend_to_length( + &self, + #[wasm_bindgen(param_description = "The length to extend to")] length: usize, + ) -> JsResult { Ok(self.as_ref().extend_to_length(length)?.into_owned().into()) } - pub fn with_segments(&self, start: usize, stop: usize) -> Option { - let range = start..stop; + /// Returns a new version with the segments from start to end (exclusive). + /// + /// Returns undefined if the start or end index is out of bounds. + #[wasm_bindgen(js_name = "withSegments")] + pub fn with_segments( + &self, + #[wasm_bindgen(param_description = "The start index")] start: usize, + #[wasm_bindgen(param_description = "The end index")] end: usize, + ) -> Option { + let range = start..end; Some(self.as_ref().with_segments(range)?.into()) } + /// The number of segments in the version. #[wasm_bindgen(getter)] pub fn length(&self) -> usize { self.as_ref().segment_count() } - /// Create a new version with local segment stripped. + /// Returns the version without the local part. E.g. `1+2.3` becomes `1`. + #[wasm_bindgen(js_name = "stripLocal")] pub fn strip_local(&self) -> Self { self.as_ref().strip_local().into_owned().into() } - /// Returns a new version where the major segment of this version has been bumped. + /// Returns a new version where the major segment of this version has been + /// bumped. + #[wasm_bindgen(js_name = "bumpMajor")] pub fn bump_major(&self) -> JsResult { Ok(self.as_ref().bump(VersionBumpType::Major).map(Into::into)?) } - /// Returns a new version where the minor segment of this version has been bumped. + /// Returns a new version where the minor segment of this version has been + /// bumped. + #[wasm_bindgen(js_name = "bumpMinor")] pub fn bump_minor(&self) -> JsResult { Ok(self.as_ref().bump(VersionBumpType::Minor).map(Into::into)?) } - /// Returns a new version where the patch segment of this version has been bumped. + /// Returns a new version where the patch segment of this version has been + /// bumped. + #[wasm_bindgen(js_name = "bumpPatch")] pub fn bump_patch(&self) -> JsResult { Ok(self.as_ref().bump(VersionBumpType::Patch).map(Into::into)?) } - /// Returns a new version where the last segment of this version has been bumped. + /// Returns a new version where the last segment of this version has been + /// bumped. + #[wasm_bindgen(js_name = "bumpLast")] pub fn bump_last(&self) -> JsResult { Ok(self.as_ref().bump(VersionBumpType::Last).map(Into::into)?) } - /// Returns a new version where the given segment of this version has been bumped. - pub fn bump_segment(&self, index: i32) -> JsResult { + /// Returns a new version where the given segment of this version has been + /// bumped. + #[wasm_bindgen(js_name = "bumpSegment")] + pub fn bump_segment( + &self, + #[wasm_bindgen(param_description = "The index of the segment to bump")] index: i32, + ) -> JsResult { Ok(self .as_ref() .bump(VersionBumpType::Segment(index)) .map(Into::into)?) } - /// Returns a new version where the last segment is an "alpha" segment (ie. `.0a0`) + /// Returns a new version where the last segment is an "alpha" segment (ie. + /// `.0a0`) + #[wasm_bindgen(js_name = "withAlpha")] pub fn with_alpha(&self) -> Self { self.as_ref().with_alpha().into_owned().into() } - pub fn equals(&self, other: &Self) -> bool { + /// Compares this version with another version. Returns `true` if the + /// versions are considered equal. + /// + /// Note that two version strings can be considered equal even if they are + /// not exactly the same. For example, `1.0` and `1` are considered equal. + #[wasm_bindgen(js_name = "equals")] + pub fn equals( + &self, + #[wasm_bindgen(param_description = "The version to compare with")] other: &Self, + ) -> bool { self.as_ref() == other.as_ref() } - pub fn compare(&self, other: &Self) -> i8 { + /// Compare two versions. + /// + /// Returns `-1` if this instance should be ordered before `other`, `0` if + /// this version and `other` are considered equal, `1` if this version + /// should be ordered after `other`. + pub fn compare( + &self, + #[wasm_bindgen(param_description = "The version to compare with")] other: &Self, + ) -> i8 { match self.as_ref().cmp(other.as_ref()) { Ordering::Less => -1, Ordering::Equal => 0, diff --git a/js-rattler/crate/version_spec.rs b/js-rattler/crate/version_spec.rs index 198c8cdf6..cbf5ca88e 100644 --- a/js-rattler/crate/version_spec.rs +++ b/js-rattler/crate/version_spec.rs @@ -1,9 +1,13 @@ -use crate::version::JsVersion; -use crate::{JsParseStrictness, JsResult}; -use rattler_conda_types::VersionSpec; +use rattler_conda_types::{ParseStrictness, VersionSpec}; use wasm_bindgen::prelude::wasm_bindgen; -#[wasm_bindgen] +use crate::parse_strictness::JsParseStrictness; +use crate::{version::JsVersion, JsResult}; + +/// Represents a version specification in the conda ecosystem. +/// +/// @public +#[wasm_bindgen(js_name = "VersionSpec")] #[repr(transparent)] #[derive(Eq, PartialEq)] pub struct JsVersionSpec { @@ -28,19 +32,36 @@ impl AsRef for JsVersionSpec { } } -#[wasm_bindgen] +#[wasm_bindgen(js_class = "VersionSpec")] impl JsVersionSpec { + /// Constructs a new VersionSpec object from a string representation. #[wasm_bindgen(constructor)] - pub fn new(version_spec: &str, parse_strictness: JsParseStrictness) -> JsResult { - let spec = VersionSpec::from_str(version_spec, parse_strictness.into())?; + pub fn new( + #[wasm_bindgen(param_description = "The string representation of the version spec.")] + version_spec: &str, + #[wasm_bindgen(param_description = "The strictness of the parser.")] + parse_strictness: Option, + ) -> JsResult { + let parse_strictness = parse_strictness + .map(TryFrom::try_from) + .transpose()? + .unwrap_or(ParseStrictness::Lenient); + + let spec = VersionSpec::from_str(version_spec, parse_strictness)?; Ok(spec.into()) } + /// Returns the string representation of the version spec. + #[wasm_bindgen(js_name = "toString")] pub fn as_str(&self) -> String { format!("{}", self.as_ref()) } - pub fn matches(&self, version: &JsVersion) -> bool { + /// Returns true if the version matches this version spec. + pub fn matches( + &self, + #[wasm_bindgen(param_description = "The version to match")] version: &JsVersion, + ) -> bool { self.as_ref().matches(version.as_ref()) } } diff --git a/js-rattler/src/Version.ts b/js-rattler/src/Version.ts index e523e9cff..53e331663 100644 --- a/js-rattler/src/Version.ts +++ b/js-rattler/src/Version.ts @@ -1,215 +1 @@ -import { JsVersion } from "../pkg"; - -/** - * Represents a version in the conda ecosystem. - * - * @public - */ -export class Version { - /** @internal */ - native: JsVersion; - - /** - * Constructs a new Version object from a string representation. - * - * @param version - The string representation of the version. - */ - constructor(version: string) { - this.native = new JsVersion(version); - } - - /** - * Constructs a new instance from a rust native object. - * - * @internal - */ - private static fromNative(version: JsVersion): Version { - const result: Version = Object.create(Version.prototype); - result.native = version; - return result; - } - - /** - * Compares this version with another version. Returns `true` if the - * versions are considered equal. - * - * Note that two version strings can be considered equal even if they are - * not exactly the same. For example, `1.0` and `1` are considered equal. - * - * @param other - The version to compare with. - */ - equals(other: Version): boolean { - return this.native.equals(other.native); - } - - /** - * Returns the string representation of the version. - * - * An attempt is made to return the version in the same format as the input - * string but this is not guaranteed. - */ - toString(): string { - return this.native.as_str(); - } - - /** The epoch part of the version. E.g. `1` in `1!2.3`. */ - public get epoch(): number | undefined { - return this.native.epoch; - } - - /** `true` if the version has a local part. E.g. `2.3` in `1+2.3`. */ - public get hasLocal(): boolean { - return this.native.has_local; - } - - /** - * `true` if the version is considered a development version. - * - * A development version is a version that contains the string `dev` in the - * version string. - */ - public get isDev(): boolean { - return this.native.is_dev; - } - - /** - * Returns the major and minor part of the version if the version does not - * represent a typical major minor version. If any of the parts are not a - * single number, undefined is returned. - */ - public asMajorMinor(): [number, number] | undefined { - const parts = this.native.as_major_minor(); - if (!parts) { - return undefined; - } - return [parts[0], parts[1]]; - } - - /** - * Returns true if this version starts with the other version. This is - * defined as the other version being a prefix of this version. - * - * @param other - The version to check if this version starts with. - */ - public startsWith(other: Version): boolean { - return this.native.starts_with(other.native); - } - - /** - * Returns true if this version is compatible with the other version. - * - * @param other - The version to check if this version is compatible with. - */ - public compatibleWith(other: Version): boolean { - return this.native.compatible_with(other.native); - } - - /** - * Pop the last `n` segments from the version. - * - * @param n - The number of segments to pop from the version. - */ - public popSegments(n: number): Version | undefined { - let native = this.native.pop_segments(n); - if (!native) { - return undefined; - } - return Version.fromNative(native); - } - - /** - * Extend the version to the given length by adding zeros. If the version is - * already at the specified length or longer the original version will be - * returned. - * - * @param length - The length to extend the version to. - */ - public extendToLength(length: number): Version { - return Version.fromNative(this.native.extend_to_length(length)); - } - - /** - * Returns a new version with the segments from start to end (exclusive). - * - * Returns undefined if the start or end index is out of bounds. - * - * @param start - The start index of the segment. - * @param end - The end index of the segment. - */ - public withSegments(start: number, end: number): Version | undefined { - let native = this.native.with_segments(start, end); - if (!native) { - return undefined; - } - return Version.fromNative(native); - } - - /** The number of segments in the version. */ - public get length(): number { - return this.native.length; - } - - /** Returns the version without the local part. E.g. `1+2.3` becomes `1`. */ - public stripLocal(): Version { - return Version.fromNative(this.native.strip_local()); - } - - /** - * Returns a new version where the major segment of this version has been - * bumped. - */ - public bumpMajor(): Version { - return Version.fromNative(this.native.bump_major()); - } - - /** - * Returns a new version where the minor segment of this version has been - * bumped. - */ - public bumpMinor(): Version { - return Version.fromNative(this.native.bump_minor()); - } - - /** - * Returns a new version where the patch segment of this version has been - * bumped. - */ - public bumpPatch(): Version { - return Version.fromNative(this.native.bump_patch()); - } - - /** - * Returns a new version where the last segment of this version has been - * bumped. - */ - public bumpLast(): Version { - return Version.fromNative(this.native.bump_last()); - } - - /** - * Returns a new version where the given segment of this version has been - * bumped. - */ - public bumpSegment(segment: number): Version { - return Version.fromNative(this.native.bump_segment(segment)); - } - - /** - * Returns a new version where the last segment is an "alpha" segment (ie. - * `.0a0`) - */ - public withAlpha(): Version { - return Version.fromNative(this.native.with_alpha()); - } - - /** - * Compare two versions. - * - * Returns `-1` if this instance should be ordered before `other`, `0` if - * this version and `other` are considered equal, `1` if this version should - * be ordered after `other`. - */ - public compare(other: Version): number { - return this.native.compare(other.native); - } -} +export { Version } from "../pkg"; diff --git a/js-rattler/src/VersionSpec.ts b/js-rattler/src/VersionSpec.ts index f3dbf8095..1254641e3 100644 --- a/js-rattler/src/VersionSpec.ts +++ b/js-rattler/src/VersionSpec.ts @@ -1,46 +1 @@ -import { ParseStrictness, JsVersionSpec } from "../pkg"; -import { Version } from "./Version"; - -/** - * Represents a version specification in the conda ecosystem. - * - * @public - */ -export class VersionSpec { - /** @internal */ - native: JsVersionSpec; - - /** - * Constructs a new VersionSpec object from a string representation. - * - * @param spec - The string representation of the version spec. - * @param strictness - The strictness used when parsing the version spec. - */ - constructor( - spec: string, - strictness: ParseStrictness = ParseStrictness.Strict, - ) { - this.native = new JsVersionSpec(spec, strictness); - } - - /** - * Constructs a new instance from a rust native object. - * - * @internal - */ - private static fromNative(version: JsVersionSpec): VersionSpec { - const result: VersionSpec = Object.create(VersionSpec.prototype); - result.native = version; - return result; - } - - /** Returns the string representation of the version. */ - toString(): string { - return this.native.as_str(); - } - - /** Returns true if the version matches this version spec. */ - matches(version: Version): boolean { - return this.native.matches(version.native); - } -} +export { VersionSpec } from "../pkg";