Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

gleam / compiler-core / src / requirement.rs
5.7 kB 211 lines
1// SPDX-License-Identifier: Apache-2.0 2// SPDX-FileCopyrightText: 2023 The Gleam contributors 3 4use std::fmt; 5use std::str::FromStr; 6 7use crate::Error; 8use crate::error::Result; 9use crate::io::make_relative; 10use camino::{Utf8Path, Utf8PathBuf}; 11use ecow::EcoString; 12use hexpm::version::Range; 13use serde::Deserialize; 14use serde::de::{self, Deserializer, MapAccess, Visitor}; 15use serde::ser::{Serialize, SerializeMap, Serializer}; 16 17#[derive(Deserialize, Debug, PartialEq, Eq, Clone)] 18#[serde(untagged, remote = "Self", deny_unknown_fields)] 19pub enum Requirement { 20 Hex { 21 #[serde(deserialize_with = "deserialise_range")] 22 version: Range, 23 }, 24 25 Path { 26 path: Utf8PathBuf, 27 }, 28 29 Git { 30 git: EcoString, 31 #[serde(rename = "ref")] 32 ref_: EcoString, 33 #[serde(default)] 34 path: Option<Utf8PathBuf>, 35 }, 36} 37 38impl Requirement { 39 pub fn hex(range: &str) -> Result<Requirement> { 40 Ok(Requirement::Hex { 41 version: Range::new(range.to_string()).map_err(|e| Error::InvalidVersionFormat { 42 input: range.to_string(), 43 error: e.to_string(), 44 })?, 45 }) 46 } 47 48 pub fn path(path: &str) -> Requirement { 49 Requirement::Path { path: path.into() } 50 } 51 52 pub fn git(url: &str, ref_: &str) -> Requirement { 53 Requirement::Git { 54 git: url.into(), 55 ref_: ref_.into(), 56 path: None, 57 } 58 } 59 60 pub fn git_with_path(url: &str, ref_: &str, path: &str) -> Requirement { 61 Requirement::Git { 62 git: url.into(), 63 ref_: ref_.into(), 64 path: Some(path.into()), 65 } 66 } 67 68 pub fn to_toml(&self, root_path: &Utf8Path) -> String { 69 match self { 70 Requirement::Hex { version: range } => { 71 format!(r#"{{ version = "{range}" }}"#) 72 } 73 Requirement::Path { path } => { 74 format!( 75 r#"{{ path = "{}" }}"#, 76 make_relative(root_path, path).as_str().replace('\\', "/") 77 ) 78 } 79 Requirement::Git { 80 git: url, 81 ref_, 82 path, 83 } => match path { 84 Some(path) => { 85 format!(r#"{{ git = "{url}", ref = "{ref_}", path = "{path}" }}"#) 86 } 87 None => format!(r#"{{ git = "{url}", ref = "{ref_}" }}"#), 88 }, 89 } 90 } 91} 92 93// Serialization 94 95impl Serialize for Requirement { 96 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 97 where 98 S: Serializer, 99 { 100 let mut map = serializer.serialize_map(Some(1))?; 101 match self { 102 Requirement::Hex { version: range } => map.serialize_entry("version", range)?, 103 Requirement::Path { path } => map.serialize_entry("path", path)?, 104 Requirement::Git { 105 git: url, 106 ref_, 107 path, 108 } => { 109 map.serialize_entry("git", url)?; 110 map.serialize_entry("ref", ref_)?; 111 if let Some(path) = path { 112 map.serialize_entry("path", path)?; 113 } 114 } 115 } 116 map.end() 117 } 118} 119 120// Deserialization 121 122fn deserialise_range<'de, D>(deserializer: D) -> Result<Range, D::Error> 123where 124 D: Deserializer<'de>, 125{ 126 let version = String::deserialize(deserializer)?; 127 Range::new(version).map_err(de::Error::custom) 128} 129 130#[derive(Debug, Copy, Clone)] 131pub struct Void; 132 133impl FromStr for Requirement { 134 type Err = Error; 135 136 fn from_str(s: &str) -> Result<Self, Self::Err> { 137 Requirement::hex(s) 138 } 139} 140 141struct RequirementVisitor; 142 143impl<'de> Visitor<'de> for RequirementVisitor { 144 type Value = Requirement; 145 146 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 147 formatter.write_str("string or map") 148 } 149 150 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> 151 where 152 E: de::Error, 153 { 154 match value.parse::<Requirement>() { 155 Ok(value) => Ok(value), 156 Err(error) => Err(de::Error::custom(error)), 157 } 158 } 159 160 fn visit_map<M>(self, visitor: M) -> Result<Self::Value, M::Error> 161 where 162 M: MapAccess<'de>, 163 { 164 Requirement::deserialize(de::value::MapAccessDeserializer::new(visitor)) 165 } 166} 167 168impl<'de> Deserialize<'de> for Requirement { 169 fn deserialize<D>(deserializer: D) -> Result<Requirement, D::Error> 170 where 171 D: Deserializer<'de>, 172 { 173 deserializer.deserialize_any(RequirementVisitor) 174 } 175} 176 177#[cfg(test)] 178mod tests { 179 180 use super::*; 181 use std::collections::HashMap; 182 183 #[test] 184 fn read_requirement() { 185 let toml = r#" 186 short = "~> 0.5" 187 hex = { version = "~> 1.0.0" } 188 local = { path = "/path/to/package" } 189 github = { git = "https://github.com/gleam-lang/otp.git", ref = "4d34935" } 190 "#; 191 let deps: HashMap<String, Requirement> = toml::from_str(toml).unwrap(); 192 assert_eq!(deps["short"], Requirement::hex("~> 0.5").unwrap()); 193 assert_eq!(deps["hex"], Requirement::hex("~> 1.0.0").unwrap()); 194 assert_eq!(deps["local"], Requirement::path("/path/to/package")); 195 assert_eq!( 196 deps["github"], 197 Requirement::git("https://github.com/gleam-lang/otp.git", "4d34935") 198 ); 199 } 200 201 #[test] 202 fn read_wrong_version() { 203 let toml = r#" 204 short = ">= 2.0 and < 3.0.0" 205 "#; 206 207 let error = 208 toml::from_str::<HashMap<String, Requirement>>(toml).expect_err("invalid version"); 209 insta::assert_snapshot!(error.to_string()); 210 } 211}