package_parser/pkgs/python/
pysetup.rs1use tree_sitter::{Parser, Query, QueryCursor};
2
3use crate::error::SourcePkgError;
4use crate::pkgs::common::model::{Package, PackageManifest};
5
6use crate::pkgs::pyrequirements::PyRequirements;
7
8use std::path::Path;
9
10static SETUP_QUERY_STR: &str = "
11(
12 call
13 function: (identifier) @function
14 arguments: (
15 argument_list
16 (
17 keyword_argument
18 name: (identifier) @kw-install-requires-name
19 value: (_) @kw-value
20 )
21 )
22 (#eq? @function \"setup\")
23 (#eq? @kw-install-requires-name \"install_requires\")
24)
25";
26
27lazy_static::lazy_static! {
28 static ref SETUP_QUERY: Query = {
29 Query::new(&tree_sitter_python::language(), SETUP_QUERY_STR).unwrap()
30 };
31 static ref STRING_QUERY: Query = {
32 Query::new(&tree_sitter_python::language(), "(string (string_content) @str)").unwrap()
33 };
34}
35
36fn parse_strings(node: tree_sitter::Node, content: &[u8]) -> Vec<String> {
37 let mut query_cursor = QueryCursor::new();
38 let mut strings = vec![];
39 for m in query_cursor.matches(&STRING_QUERY, node, content) {
40 let value_node = m.captures.first().unwrap().node;
41 let value = value_node.utf8_text(content).unwrap();
42 strings.push(value.to_string());
43 }
44
45 strings
46}
47
48pub struct PySetup {}
49
50impl PySetup {
51 pub fn new() -> Self {
52 Self {}
53 }
54
55 fn parse(path: impl AsRef<Path>) -> Result<Package, SourcePkgError> {
56 let content = std::fs::read_to_string(path.as_ref())?;
57 let content_raw = content.as_bytes();
58
59 let mut parser = Parser::new();
60 parser
61 .set_language(&tree_sitter_python::language())
62 .unwrap();
63 let tree = parser.parse(content_raw, None).unwrap();
64 let root_node = tree.root_node();
65
66 let mut query_cursor = QueryCursor::new();
67 for m in query_cursor.matches(&SETUP_QUERY, root_node, content_raw) {
68 let value_node = m.captures.get(2).unwrap().node;
69
70 let packages = match value_node.grammar_name() {
71 "list" => parse_strings(value_node, content_raw),
72 "identifier" => {
73 let var_name = value_node.utf8_text(content_raw).unwrap();
74 let query = Query::new(
75 &tree_sitter_python::language(),
76 &format!(
77 "
78 (
79 assignment
80 left: (identifier) @name
81 right: (list) @value
82 (#eq? @name \"{}\")
83 )
84 ",
85 var_name
86 ),
87 )
88 .unwrap();
89
90 let mut query_cursor = QueryCursor::new();
91 if let Some(m) = query_cursor.matches(&query, root_node, content_raw).next() {
92 let value_node = m.captures.get(1).unwrap().node;
93 parse_strings(value_node, content_raw)
94 } else {
95 continue;
96 }
97 }
98 _ => continue,
99 };
100
101 if packages.is_empty() {
102 continue;
103 }
104
105 let mut requirements = packages.join("\n");
106 requirements.push('\n');
107 let parsed = PyRequirements::parse_requirement_content(&requirements)?;
108
109 dbg!(parsed);
110 }
111
112 let package = Package::default();
113 Ok(package)
114 }
115}
116
117#[async_trait::async_trait]
118impl PackageManifest for PySetup {
119 fn get_name(&self) -> String {
120 "pypi".to_string()
121 }
122
123 async fn recognize(&self, path: &Path) -> Result<Package, SourcePkgError> {
124 Self::parse(path)
125 }
126
127 fn file_name_patterns(&self) -> &'static [&'static str] {
128 &["setup.py", "*setup.py", "setup*.py"]
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135
136 #[test]
137 fn test_parse_pysetup_1() {
138 let filepath = Path::new(concat!(
139 env!("CARGO_MANIFEST_DIR"),
140 "/testdata/pypi/setup.py/simple-setup.py"
141 ));
142
143 let p = PySetup::parse(filepath).unwrap();
144 println!("{:?}", p);
145 }
146
147 #[test]
148 fn test_parse_pysetup_2() {
149 let filepath = Path::new(concat!(
150 env!("CARGO_MANIFEST_DIR"),
151 "/testdata/pypi/setup.py/pipdeptree_setup.py"
152 ));
153
154 let p = PySetup::parse(filepath).unwrap();
155 println!("{:?}", p);
156 }
157}