1use std::fmt;
5use std::path::PathBuf;
6use std::str::FromStr;
7
8use node_semver::{Range, Version};
9use nom::combinator::all_consuming;
10use nom::Err;
11
12pub use crate::error::{PackageSpecError, SpecErrorKind};
13pub use crate::gitinfo::{GitHost, GitInfo};
14use crate::parsers::package;
15
16mod error;
17mod gitinfo;
18mod parsers;
19
20#[derive(Debug, Clone, PartialEq, Eq, Hash)]
21pub enum VersionSpec {
22 Tag(String),
23 Version(Version),
24 Range(Range),
25}
26
27#[derive(Debug, Clone, PartialEq, Eq, Hash)]
28pub enum PackageSpec {
29 Dir {
30 path: PathBuf,
31 },
32 Alias {
33 name: String,
34 spec: Box<PackageSpec>,
35 },
36 Npm {
37 scope: Option<String>,
38 name: String,
39 requested: Option<VersionSpec>,
40 },
41 Git(GitInfo),
42}
43
44impl PackageSpec {
45 pub fn is_alias(&self) -> bool {
46 matches!(self, PackageSpec::Alias { .. })
47 }
48
49 pub fn is_npm(&self) -> bool {
50 use PackageSpec::*;
51 match self {
52 Alias { spec, .. } => spec.is_npm(),
53 Dir { .. } | Git(..) => false,
54 Npm { .. } => true,
55 }
56 }
57
58 pub fn target(&self) -> &PackageSpec {
59 use PackageSpec::*;
60 match self {
61 Alias { ref spec, .. } => {
62 if spec.is_alias() {
63 spec.target()
64 } else {
65 spec
66 }
67 }
68 _ => self,
69 }
70 }
71
72 pub fn target_mut(&mut self) -> &mut PackageSpec {
73 use PackageSpec::*;
74 match self {
75 Alias { spec, .. } => {
76 if spec.is_alias() {
77 spec.target_mut()
78 } else {
79 spec
80 }
81 }
82 _ => self,
83 }
84 }
85
86 pub fn requested(&self) -> String {
87 use PackageSpec::*;
88 match self {
89 Dir { path } => format!("{}", path.display()),
90 Git(info) => format!("{info}"),
91 Npm { ref requested, .. } => requested
92 .as_ref()
93 .map(|r| r.to_string())
94 .unwrap_or_else(|| "*".to_string()),
95 Alias { ref spec, .. } => {
96 format!(
97 "{}{}",
98 if let Npm { .. } = **spec { "npm:" } else { "" },
99 spec
100 )
101 }
102 }
103 }
104}
105
106impl FromStr for PackageSpec {
107 type Err = PackageSpecError;
108
109 fn from_str(s: &str) -> Result<Self, Self::Err> {
110 parse_package_spec(s)
111 }
112}
113
114impl fmt::Display for PackageSpec {
115 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116 use PackageSpec::*;
117 match self {
118 Dir { path } => write!(f, "{}", path.display()),
119 Git(info) => write!(f, "{info}"),
120 Npm {
121 ref name,
122 ref requested,
123 ..
124 } => {
125 write!(f, "{name}")?;
126 if let Some(req) = requested {
127 write!(f, "@{req}")?;
128 }
129 Ok(())
130 }
131 Alias { ref name, ref spec } => {
132 write!(f, "{name}@")?;
133 if let Npm { .. } = **spec {
134 write!(f, "npm:")?;
135 }
136 write!(f, "{spec}")
137 }
138 }
139 }
140}
141
142impl fmt::Display for VersionSpec {
143 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144 use VersionSpec::*;
145 match self {
146 Tag(tag) => write!(f, "{tag}"),
147 Version(v) => write!(f, "{v}"),
148 Range(range) => write!(f, "{range}"),
149 }
150 }
151}
152
153fn parse_package_spec<I>(input: I) -> Result<PackageSpec, PackageSpecError>
154where
155 I: AsRef<str>,
156{
157 let input = input.as_ref();
158 match all_consuming(package::package_spec)(input) {
159 Ok((_, arg)) => Ok(arg),
160 Err(err) => Err(match err {
161 Err::Error(e) | Err::Failure(e) => PackageSpecError {
162 input: input.into(),
163 offset: e.input.as_ptr() as usize - input.as_ptr() as usize,
164 kind: if let Some(kind) = e.kind {
165 kind
166 } else if let Some(ctx) = e.context {
167 SpecErrorKind::Context(ctx)
168 } else {
169 SpecErrorKind::Other
170 },
171 },
172 Err::Incomplete(_) => PackageSpecError {
173 input: input.into(),
174 offset: input.len() - 1,
175 kind: SpecErrorKind::IncompleteInput,
176 },
177 }),
178 }
179}