1+ import ast
2+ import json
3+
4+ import parso
5+ import pytest
6+
7+ from collections import OrderedDict
8+ from types import GeneratorType as generator
9+ from itertools import chain
10+ from pathlib import Path
11+
12+ from objectpath import Tree
13+ from mongoquery import Query
14+
15+ from tests .nodes import convert_node , flatten
16+ from tests .template import Template
17+
18+
19+ class Parser :
20+ def __init__ (self , file_name , nodes ):
21+
22+ sensor = Path .cwd () / "sensor"
23+ # ext = sensor / "extensions"
24+
25+ self .data = {
26+ "success" : True ,
27+ "full_path" : "" ,
28+ "message" : "" ,
29+ "start_pos" : 0 ,
30+ "nodes" : nodes ,
31+ }
32+
33+ if file_name is not None :
34+ path = lambda root , fn : root / "{}.py" .format (fn )
35+ # if file_name == "menu" or file_name == "stats":
36+ # full_path = path(ext, file_name)
37+ if file_name == "sensor" :
38+ full_path = path (Path .cwd (), file_name )
39+ else :
40+ full_path = path (sensor , file_name )
41+
42+ grammar = parso .load_grammar ()
43+ module = grammar .parse (path = full_path )
44+ self .data ["success" ] = len (grammar .iter_errors (module )) == 0
45+
46+ if self .data ["success" ]:
47+ self .data ["message" ] = "Syntax: valid"
48+ if file_name is not None :
49+ self .data ["nodes" ] = convert_node (ast .parse (full_path .read_text ()))
50+ self .data ["code" ] = full_path .read_text ()
51+
52+ else :
53+ self .data ["message" ] = grammar .iter_errors (module )[0 ].message
54+ self .data ["start_pos" ] = grammar .iter_errors (module )[0 ].start_pos [0 ]
55+
56+ @property
57+ def nodes (self ):
58+ return self .data ["nodes" ]
59+
60+ n = nodes
61+
62+ @property
63+ def success (self ):
64+ return self .data ["success" ]
65+
66+ @property
67+ def code (self ):
68+ return self .data ["code" ]
69+
70+ def query (self , pattern ):
71+ nodes = Template (pattern ).process (self .code )
72+ if isinstance (nodes , list ) and len (nodes ) == 1 :
73+ nodes = nodes [0 ]
74+
75+ return Parser (None , nodes )
76+
77+ def query_raw (self , pattern ):
78+ nodes = Template (pattern ).process (self .code , True )
79+ if isinstance (nodes , list ) and len (nodes ) == 1 :
80+ nodes = [flatten (convert_node (node )) for node in nodes [0 ].body ]
81+ return Parser (None , nodes )
82+
83+ def last_line (self ):
84+ return flatten (self .nodes ["body" ][- 1 ])
85+
86+ @property
87+ def message (self ):
88+ return "{} on or around line {} in `{}`." .format (
89+ self .data ["message" ], self .data ["start_pos" ], self .data ["full_path" ]
90+ )
91+
92+ def match (self , template ):
93+ return Parser (None , list (filter (Query (template ).match , self .nodes )))
94+
95+ def execute (self , expr ):
96+ result = Tree (self .nodes ).execute (expr )
97+ if isinstance (result , (generator , chain , map )):
98+ process = list (result )
99+ return (
100+ Parser (None , process [0 ]) if len (process ) == 1 else Parser (None , process )
101+ )
102+ else :
103+ return Parser (None , result )
104+
105+ ex = execute
106+
107+ def exists (self ):
108+ return bool (self .nodes )
109+
110+ def calls (self ):
111+ nodes = self .execute ("$.body[@.type is 'Expr' and @.value.type is 'Call']" ).n
112+ node_list = [nodes ] if isinstance (nodes , dict ) else nodes
113+
114+ return Parser (None , [flatten (node ) for node in node_list ])
115+
116+ def assign_ (self ):
117+ return Parser (None , [flatten (self .execute ("$.body[@.type is 'Assign']" ).n )])
118+
119+ def assigns (self ):
120+ return Parser (
121+ None ,
122+ [flatten (node ) for node in self .execute ("$.body[@.type is 'Assign']" ).n ],
123+ )
124+
125+ def globals (self , name ):
126+ return name in self .execute ("$.body[@.type is 'Global'].names" ).n
127+
128+ def defines (self , name ):
129+ return self .execute (
130+ "$.body[@.type is 'FunctionDef' and @.name is '{}'].(name, args, body, decorator_list)" .format (
131+ name
132+ )
133+ )
134+
135+ def class_ (self , name ):
136+ return self .execute (
137+ "$.body[@.type is 'ClassDef' and @.name is '{}'].(name, args, body)" .format (
138+ name
139+ )
140+ )
141+
142+ def decorators (self ):
143+ return Parser (None , [flatten (self .execute ("$.decorator_list" ).n )])
144+
145+ def returns (self , name ):
146+ return name == self .execute ("$.body[@.type is 'Return'].value.id" ).n
147+
148+ def returns_call (self ):
149+ return Parser (None , [flatten (self .execute ("$.body[@.type is 'Return']" ).n )])
150+
151+ def method (self , name ):
152+ return self .execute (
153+ "$..body[@.type is 'FunctionDef' and @.name is '{}']" .format (name )
154+ )
155+
156+ def has_arg (self , name , pos = 0 ):
157+ nodes = self .execute ("$.args.args.arg" ).n
158+ return nodes [pos ] if isinstance (nodes , list ) else nodes
159+
160+ def imports (self , name ):
161+ return name in self .execute ("$.body[@.type is 'Import'].names..name" ).n
162+
163+ def for_ (self ):
164+ for_body = self .execute ("$.body[@.type is 'For'].body" ).n
165+ iterators = self .execute ("$.body[@.type is 'For'].(target, iter)" ).n
166+ return Parser (None , [flatten (for_body ), flatten (iterators )])
167+
168+ def from_imports (self , mod , alias ):
169+ nodes = self .execute (
170+ "$.body[@.type is 'ImportFrom' and @.module is '{}'].names..name" .format (
171+ mod
172+ )
173+ ).n
174+ return alias in (nodes if isinstance (nodes , list ) else [nodes ])
175+
176+
177+ @pytest .fixture
178+ def parse ():
179+ def _parse (file_name ):
180+ return Parser (file_name , {})
181+
182+ return _parse
0 commit comments