1#![warn(clippy::pedantic)]
34#![warn(clippy::nursery)]
35#![warn(clippy::cargo)]
36
37use crate::parser::{gomod, Directive};
38use winnow::Parser;
39
40mod combinator;
41pub mod parser;
42
43#[derive(Debug, Default, PartialEq, Eq)]
44pub struct GoMod {
45 pub comment: Vec<String>,
46 pub module: String,
47 pub go: Option<String>,
48 pub toolchain: Option<String>,
49 pub require: Vec<ModuleDependency>,
50 pub exclude: Vec<ModuleDependency>,
51 pub replace: Vec<ModuleReplacement>,
52 pub retract: Vec<ModuleRetract>,
53}
54
55impl std::str::FromStr for GoMod {
56 type Err = String;
57
58 fn from_str(input: &str) -> Result<Self, Self::Err> {
59 let mut res = Self::default();
60
61 for directive in &mut gomod.parse(input).map_err(|e| e.to_string())? {
62 match directive {
63 Directive::Comment(d) => res.comment.push((**d).to_string()),
64 Directive::Module(d) => res.module = (**d).to_string(),
65 Directive::Go(d) => res.go = Some((**d).to_string()),
66 Directive::Toolchain(d) => res.toolchain = Some((**d).to_string()),
67 Directive::Require(d) => res.require.append(d),
68 Directive::Exclude(d) => res.exclude.append(d),
69 Directive::Replace(d) => res.replace.append(d),
70 Directive::Retract(d) => res.retract.append(d),
71 }
72 }
73
74 Ok(res)
75 }
76}
77
78#[derive(Debug, PartialEq, Eq)]
79pub struct Module {
80 pub module_path: String,
81 pub version: String,
82}
83
84#[derive(Debug, PartialEq, Eq)]
85pub struct ModuleDependency {
86 pub module: Module,
87 pub indirect: bool,
88}
89
90#[derive(Debug, PartialEq, Eq)]
91pub struct ModuleReplacement {
92 pub module_path: String,
93 pub version: Option<String>,
94 pub replacement: Replacement,
95}
96
97#[derive(Debug, PartialEq, Eq)]
98pub enum Replacement {
99 FilePath(String),
100 Module(Module),
101}
102
103#[derive(Debug, PartialEq, Eq)]
104pub enum ModuleRetract {
105 Single(String),
106 Range(String, String),
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112 use indoc::indoc;
113 use std::str::FromStr;
114
115 #[test]
116 fn test_parse_complete() {
117 let input = indoc! {r#"
118 // Complete example
119
120 module github.com/complete
121
122 go 1.21
123
124 toolchain go1.21.1
125
126 require golang.org/x/net v0.20.0
127
128 exclude golang.org/x/net v0.19.1
129
130 replace golang.org/x/net v0.19.0 => example.com/fork/net v0.19.1
131
132 retract v1.0.0
133 "#};
134
135 let go_mod = GoMod::from_str(input).unwrap();
136
137 assert_eq!(go_mod.module, "github.com/complete".to_string());
138 assert_eq!(go_mod.go, Some("1.21".to_string()));
139 assert_eq!(go_mod.toolchain, Some("go1.21.1".to_string()));
140 assert_eq!(
141 go_mod.require,
142 vec![ModuleDependency {
143 module: Module {
144 module_path: "golang.org/x/net".to_string(),
145 version: "v0.20.0".to_string()
146 },
147 indirect: false
148 }]
149 );
150 assert_eq!(
151 go_mod.exclude,
152 vec![ModuleDependency {
153 module: Module {
154 module_path: "golang.org/x/net".to_string(),
155 version: "v0.19.1".to_string()
156 },
157 indirect: false
158 }]
159 );
160 assert_eq!(
161 go_mod.replace,
162 vec![ModuleReplacement {
163 module_path: "golang.org/x/net".to_string(),
164 version: Some("v0.19.0".to_string()),
165 replacement: Replacement::Module(Module {
166 module_path: "example.com/fork/net".to_string(),
167 version: "v0.19.1".to_string(),
168 })
169 }]
170 );
171 assert_eq!(
172 go_mod.retract,
173 vec![ModuleRetract::Single("v1.0.0".to_string())]
174 );
175 assert_eq!(go_mod.comment, vec!["Complete example".to_string()]);
176 }
177
178 #[test]
179 fn test_invalid_content() {
180 let input = indoc! {r#"
181 modulegithub.com/no-space
182 "#};
183
184 let go_mod = GoMod::from_str(input);
185
186 assert!(go_mod.is_err());
187 }
188
189 #[test]
190 fn test_no_line_ending() {
191 let input = indoc! {r#"
192 module github.com/no-line-ending
193
194 require (
195 golang.org/x/net v0.20.0
196 )"#};
197
198 let go_mod = GoMod::from_str(input).unwrap();
199
200 assert_eq!(go_mod.module, "github.com/no-line-ending".to_string());
201 assert_eq!(
202 go_mod.require,
203 vec![ModuleDependency {
204 module: Module {
205 module_path: "golang.org/x/net".to_string(),
206 version: "v0.20.0".to_string()
207 },
208 indirect: false
209 }]
210 );
211 }
212}