6
6
import os
7
7
import pathlib
8
8
import sys
9
- from typing import Optional , Sequence
9
+ from typing import Dict , List , Optional , Sequence , Tuple , Union
10
10
11
11
LIB_ROOT = pathlib .Path (__file__ ).parent / "lib" / "python"
12
12
sys .path .insert (0 , os .fspath (LIB_ROOT ))
13
13
14
+ import tomli
14
15
from importlib_metadata import metadata
15
16
from packaging .requirements import Requirement
16
17
18
+ DEFAULT_SEVERITY = 3
19
+
17
20
18
21
def parse_args (argv : Optional [Sequence [str ]] = None ):
19
22
if argv is None :
20
23
argv = sys .argv [1 :]
21
24
parser = argparse .ArgumentParser (
22
25
description = "Check for installed packages against requirements"
23
26
)
24
- parser .add_argument (
25
- "REQUIREMENTS" , type = str , help = "Path to requirements.[txt, in]" , nargs = "+"
26
- )
27
+ parser .add_argument ("FILEPATH" , type = str , help = "Path to requirements.[txt, in]" )
27
28
28
29
return parser .parse_args (argv )
29
30
@@ -39,32 +40,88 @@ def parse_requirements(line: str) -> Optional[Requirement]:
39
40
return None
40
41
41
42
42
- def main ():
43
- args = parse_args ()
43
+ def process_requirements (req_file : pathlib .Path ) -> List [Dict [str , Union [str , int ]]]:
44
+ diagnostics = []
45
+ for n , line in enumerate (req_file .read_text (encoding = "utf-8" ).splitlines ()):
46
+ if line .startswith (("#" , "-" , " " )) or line == "" :
47
+ continue
48
+
49
+ req = parse_requirements (line )
50
+ if req :
51
+ try :
52
+ # Check if package is installed
53
+ metadata (req .name )
54
+ except :
55
+ diagnostics .append (
56
+ {
57
+ "line" : n ,
58
+ "character" : 0 ,
59
+ "endLine" : n ,
60
+ "endCharacter" : len (req .name ),
61
+ "package" : req .name ,
62
+ "code" : "not-installed" ,
63
+ "severity" : DEFAULT_SEVERITY ,
64
+ }
65
+ )
66
+ return diagnostics
44
67
68
+
69
+ def get_pos (lines : List [str ], text : str ) -> Tuple [int , int , int , int ]:
70
+ for n , line in enumerate (lines ):
71
+ index = line .find (text )
72
+ if index >= 0 :
73
+ return n , index , n , index + len (text )
74
+ return (0 , 0 , 0 , 0 )
75
+
76
+
77
+ def process_pyproject (req_file : pathlib .Path ) -> List [Dict [str , Union [str , int ]]]:
45
78
diagnostics = []
46
- for req_file in args .REQUIREMENTS :
47
- req_file = pathlib .Path (req_file )
48
- if req_file .exists ():
49
- lines = req_file .read_text (encoding = "utf-8" ).splitlines ()
50
- for n , line in enumerate (lines ):
51
- if line .startswith (("#" , "-" , " " )) or line == "" :
52
- continue
53
-
54
- req = parse_requirements (line )
55
- if req :
56
- try :
57
- # Check if package is installed
58
- metadata (req .name )
59
- except :
60
- diagnostics .append (
61
- {
62
- "line" : n ,
63
- "package" : req .name ,
64
- "code" : "not-installed" ,
65
- "severity" : 3 ,
66
- }
67
- )
79
+ try :
80
+ raw_text = req_file .read_text (encoding = "utf-8" )
81
+ pyproject = tomli .loads (raw_text )
82
+ except :
83
+ return diagnostics
84
+
85
+ lines = raw_text .splitlines ()
86
+ reqs = pyproject .get ("project" , {}).get ("dependencies" , [])
87
+ for raw_req in reqs :
88
+ req = parse_requirements (raw_req )
89
+ n , start , _ , end = get_pos (lines , raw_req )
90
+ if req :
91
+ try :
92
+ # Check if package is installed
93
+ metadata (req .name )
94
+ except :
95
+ diagnostics .append (
96
+ {
97
+ "line" : n ,
98
+ "character" : start ,
99
+ "endLine" : n ,
100
+ "endCharacter" : end ,
101
+ "package" : req .name ,
102
+ "code" : "not-installed" ,
103
+ "severity" : DEFAULT_SEVERITY ,
104
+ }
105
+ )
106
+ return diagnostics
107
+
108
+
109
+ def get_diagnostics (req_file : pathlib .Path ) -> List [Dict [str , Union [str , int ]]]:
110
+ diagnostics = []
111
+ if not req_file .exists ():
112
+ return diagnostics
113
+
114
+ if req_file .name == "pyproject.toml" :
115
+ diagnostics = process_pyproject (req_file )
116
+ else :
117
+ diagnostics = process_requirements (req_file )
118
+
119
+ return diagnostics
120
+
121
+
122
+ def main ():
123
+ args = parse_args ()
124
+ diagnostics = get_diagnostics (pathlib .Path (args .FILEPATH ))
68
125
print (json .dumps (diagnostics , ensure_ascii = False ))
69
126
70
127
0 commit comments