1- import re
21import ast
2+ import json
33
44import parso
55import pytest
66
7+ from collections import OrderedDict
8+ from types import GeneratorType as generator
9+ from itertools import chain
710from pathlib import Path
8- from redbaron import RedBaron
9- from redbaron .utils import indent
1011
12+ from objectpath import Tree
13+ from mongoquery import Query
1114
12- class SourceCode :
13- def __init__ (self , exists , code ):
14- self .exists = exists
15- self .code = code
15+ from tests .nodes import convert_node , flatten
16+ from tests .template import Template
1617
1718
1819class Parser :
19- def __init__ (self , filename ):
20- self .code = ""
21- self .message = ""
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
2245
23- error_message = ""
24- error_start_pos = ""
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 ()
2551
26- if filename == "sensor" :
27- file_path = Path .cwd () / "sensor.py"
2852 else :
29- file_path = Path .cwd () / "sensor" / "{}.py" .format (filename )
53+ self .data ["message" ] = grammar .iter_errors (module )[0 ].message
54+ self .data ["start_pos" ] = grammar .iter_errors (module )[0 ].start_pos [0 ]
3055
31- grammar = parso . load_grammar ()
32- module = grammar . parse ( path = file_path . resolve ())
33- self .success = len ( grammar . iter_errors ( module )) == 0
56+ @ property
57+ def nodes ( self ):
58+ return self .data [ "nodes" ]
3459
35- if self .success :
36- with open (file_path .resolve (), "r" ) as source_code :
37- self .code = RedBaron (source_code .read ())
38- else :
39- error_message = grammar .iter_errors (module )[0 ].message
40- error_start_pos = grammar .iter_errors (module )[0 ].start_pos [0 ]
41- self .message = "{} on or around line {} in `{}`." .format (
42- error_message , error_start_pos , file_path .name
43- )
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 )
4476
45- def get_by_name (self , type , name , code = None ):
46- if code is None :
47- item = self .code .find_all (type , lambda node : node .name == name )
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+ )
48102 else :
49- item = code .find_all (type , lambda node : node .name == name )
103+ return Parser (None , result )
104+
105+ ex = execute
106+
107+ def exists (self ):
108+ return bool (self .nodes )
50109
51- return SourceCode (True , item [0 ]) if len (item ) > 0 else SourceCode (False , [])
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
52113
53- def get_call (self , value , code ):
54- call = code .find ("call" , lambda node : node .previous .value == value )
55- return SourceCode (True , call ) if call is not None and len (call ) > 0 else SourceCode (False , [])
114+ return Parser (None , [flatten (node ) for node in node_list ])
56115
57- def get_args (self , code ):
58- return list (
59- code .find_all ("call_argument" ).map (
60- lambda node : str (node .target ) + ":" + str (node .value ).replace ("'" , '"' )
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
61132 )
62133 )
63134
64- def get_by_value (self , type , value , code = None ):
65- if code is None :
66- item = self .code .find_all (type , lambda node : str (node .target ) == value )
67- else :
68- item = code .find_all (type , lambda node : str (node .target ) == value )
69- return SourceCode (True , item [0 ]) if len (item ) > 0 else SourceCode (False , [])
70-
71- def get_imports (self ):
72- imports = []
73- self .code .find_all (
74- "import" ,
75- lambda node : node .find_all (
76- "dotted_as_name" , lambda node : imports .append (str (node ))
77- ),
135+ def class_ (self , name ):
136+ return self .execute (
137+ "$.body[@.type is 'ClassDef' and @.name is '{}'].(name, args, body)" .format (
138+ name
139+ )
78140 )
79- return imports
80-
81- def get_from_import (self , value ):
82- imports = self .code .find_all (
83- "from_import" ,
84- lambda node : "" .join (list (node .value .node_list .map (lambda node : str (node ))))
85- == value ,
86- ).find_all ("name_as_name" )
87- return list (imports .map (lambda node : node .value ))
88-
89- def flatten (self , dictionary ):
90- def _flatten (node ):
91- trimmed = re .sub (r"\"|'" , "" , node .key .value )
92- flattened = []
93- if node .value .type is "list" :
94- for item in node .value .node_list :
95- if item .type is not "comma" :
96- flattened .append ("{}:{}" .format (trimmed , str (item )))
97- else :
98- flattened .append ("{}:{}" .format (trimmed , node .value .value ))
99-
100- return flattened
101-
102- items = list (dictionary .find_all ("dictitem" ).map (lambda node : _flatten (node )))
103- return [item for sublist in items for item in sublist ]
104-
105- def get_conditional (self , values , type , nested = False ):
106- def flat (node ):
107- if node .type == "comparison" :
108- return "{}:{}:{}" .format (
109- str (node .first ).replace ("'" , '"' ),
110- str (node .value ).replace (" " , ":" ),
111- str (node .second ).replace ("'" , '"' ),
112- )
113- elif node .type == "unitary_operator" :
114- return "{}:{}" .format (
115- str (node .value ), str (node .target ).replace ("'" , '"' )
116- )
117-
118- nodes = self .code .value if nested else self .code
119- for value in values :
120- final_node = nodes .find_all (type ).find (
121- ["comparison" , "unitary_operator" ], lambda node : flat (node ) == value
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
122172 )
123- if final_node is not None :
124- return final_node
125- return None
173+ ).n
174+ return alias in (nodes if isinstance (nodes , list ) else [nodes ])
126175
127176
128177@pytest .fixture
129178def parse ():
130- def _parse (filename ):
131- return Parser (filename )
179+ def _parse (file_name ):
180+ return Parser (file_name , {} )
132181
133182 return _parse
0 commit comments