@@ -4,8 +4,45 @@ use std::fmt::Display;
4
4
use std:: fs:: { read_to_string, File } ;
5
5
use std:: path:: PathBuf ;
6
6
7
+ use anyhow:: { bail, Result } ;
8
+ use package_version:: PackageVersion ;
9
+ use pomsky_macro:: pomsky;
10
+ use regex:: Regex ;
11
+
12
+ static REQUIREMENTS_LINE_PARSER : & str = pomsky ! (
13
+ "v" ?
14
+ (
15
+ : op( "==" | ">=" | "<=" )
16
+ )
17
+ ) ;
18
+
7
19
pub enum PyRequirementsOperator {
8
20
EqualTo ,
21
+ GreaterThan ,
22
+ LesserThan ,
23
+ }
24
+
25
+ impl PyRequirementsOperator {
26
+ /// Creates a new `PyRequirementsOperator`
27
+ ///
28
+ /// # Usage
29
+ /// ```
30
+ /// let a = PyRequirementsOperator::new("==").unwrap(); // Returns PyRequirementsOperator::EqualTo
31
+ /// let b = PyRequirementsOperator::new("BigChungus"); // Returns an Err
32
+ /// let c = PyRequirementsOperator::new("!!").unwrap(); // Also returns an Err
33
+ /// ```
34
+ fn new ( op : & str ) -> Result < Self , String > {
35
+ if op. len ( ) > 2 {
36
+ return Err ( format ! ( "Operator is {} long" , op. len( ) ) ) ;
37
+ }
38
+
39
+ match op {
40
+ "==" => Ok ( Self :: EqualTo ) ,
41
+ ">=" => Ok ( Self :: GreaterThan ) ,
42
+ "<=" => Ok ( Self :: LesserThan ) ,
43
+ _ => Err ( format ! ( "Unknown Operator: {}" , op) ) ,
44
+ }
45
+ }
9
46
}
10
47
11
48
impl Default for PyRequirementsOperator {
@@ -21,6 +58,8 @@ impl Display for PyRequirementsOperator {
21
58
"{}" ,
22
59
match self {
23
60
Self :: EqualTo => "==" ,
61
+ Self :: GreaterThan => ">=" ,
62
+ Self :: LesserThan => "<=" ,
24
63
}
25
64
)
26
65
}
@@ -29,13 +68,34 @@ impl Display for PyRequirementsOperator {
29
68
/// Represents a module in a `requirements.txt` file
30
69
pub struct PyRequirementsModule {
31
70
pub package : String ,
32
- pub version : String ,
71
+ pub version : PackageVersion ,
33
72
pub operator : PyRequirementsOperator ,
34
73
}
35
74
36
75
impl PyRequirementsModule {
37
- fn new ( raw : String ) -> Self {
38
- todo ! ( ) ;
76
+ fn new ( raw : & str ) -> Result < Self > {
77
+ let regex = Regex :: new ( REQUIREMENTS_LINE_PARSER ) . unwrap ( ) ;
78
+ let res = match regex. captures ( raw) {
79
+ Some ( caps) => caps,
80
+ None => bail ! ( "unable to parse line" ) ,
81
+ } ;
82
+
83
+ let op = res. name ( "op" ) . unwrap ( ) ;
84
+ let ( op_start, op_end) = ( op. start ( ) , op. end ( ) ) ;
85
+
86
+ Ok ( Self {
87
+ operator : match PyRequirementsOperator :: new (
88
+ res. name ( "op" ) . unwrap ( ) . as_str ( ) ,
89
+ ) {
90
+ Ok ( op) => op,
91
+ Err ( err) => bail ! ( "Op Parsing returned an error: {}" , err) ,
92
+ } ,
93
+ package : raw[ ..op_start] . to_string ( ) ,
94
+ version : match PackageVersion :: new ( & raw [ op_end..] ) {
95
+ Ok ( ver) => ver,
96
+ Err ( err) => bail ! ( "Package Versioner returned an error: {}" , err) ,
97
+ } ,
98
+ } )
39
99
}
40
100
}
41
101
@@ -53,19 +113,37 @@ pub struct PyRequirements {
53
113
54
114
impl PyRequirements {
55
115
pub fn new ( path : & PathBuf ) -> Result < ( ) , String > {
116
+ // Check if the path specified is a file
56
117
if !path. is_file ( ) {
57
118
return Err ( format ! ( "{:?} is not a file!" , path. to_str( ) . unwrap( ) ) ) ;
58
- } else {
59
- let raw: Vec < String > = read_to_string ( & path)
60
- . expect (
61
- format ! ( "Unable to read file: {:?}" , path. to_str( ) . unwrap( ) )
62
- . as_str ( ) ,
63
- )
64
- . split ( "\n " )
65
- . map ( |item| item. to_string ( ) )
66
- . collect ( ) ;
67
119
}
68
120
69
- return Ok ( ( ) ) ;
121
+ // Then check if that file is a "requirements.txt" file
122
+ // TODO: Use some magic to see if the file can be parsed
123
+ // and then use that to check instead of this
124
+ if !path. ends_with ( "requirements.txt" ) {
125
+ return Err ( format ! (
126
+ "File specified is not a 'requirements.txt' file: {:?}" ,
127
+ path. to_str( ) . unwrap( )
128
+ ) ) ;
129
+ }
130
+
131
+ let binding = read_to_string ( & path) . expect (
132
+ format ! ( "Unable to read file: {:?}" , path. to_str( ) . unwrap( ) ) . as_str ( ) ,
133
+ ) ;
134
+
135
+ let raw: Vec < & str > = binding. split ( "\n " ) . collect ( ) ;
136
+ let mut requirements = Vec :: < PyRequirementsModule > :: new ( ) ;
137
+
138
+ for ( lineno, line) in raw. iter ( ) . enumerate ( ) {
139
+ match PyRequirementsModule :: new ( line) {
140
+ Ok ( py_mod) => requirements. push ( py_mod) ,
141
+ Err ( err) => {
142
+ println ! ( "Unable to parse line {}: {}" , lineno, err)
143
+ }
144
+ }
145
+ }
146
+
147
+ Ok ( ( ) )
70
148
}
71
149
}
0 commit comments