Weird bug in python project - python

I'm working on a big project in Python, and I've run into a bizarre error I can't explain. In one of my classes, I have a private method being called during instantiation:
def _convertIndex(self, dimInd, dimName):
'''Private function that converts numbers to numbers and non-integers using
the subclass\'s convertIndex.'''
print dimInd, ' is dimInd'
try:
return int(dimName)
except:
if dimName == '*':
return 0
else:
print self.param.sets, ' is self.param.sets'
print type(self.param.sets), ' is the type of self.param.sets'
print self.param.sets[dimInd], ' is the param at dimind'
return self.param.sets[dimInd].value(dimName)
What it's printing out:
0 is dimInd
[<coremcs.SymbolicSet.SymbolicSet object at 0x10618ad90>] is self.param.sets
<type 'list'> is the type of self.param.sets
<SymbolicSet BAZ=['baz1', 'baz2', 'baz3', 'baz4']> is the param at dimind
======================================================================
ERROR: testParameterSet (GtapTest.TestGtapParameter)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/myuser/Documents/workspace/ilucmc/gtapmcs/test/GtapTest.py", line 116, in testParameterSet
pset = ParameterSet(prmFile, dstFile, GtapParameter)
File "/Users/myuser/Documents/workspace/ilucmc/coremcs/ParameterSet.py", line 103, in __init__
self.distroDict, corrDefs = AbsBaseDistro.readFile(distFile, self.paramDict)
File "/Users/myuser/Documents/workspace/ilucmc/coremcs/Distro.py", line 359, in readFile
distro = cls.parseDistro(param, target, distroType, args)
File "/Users/myuser/Documents/workspace/ilucmc/coremcs/Distro.py", line 301, in parseDistro
return cls(param, target, distro, dim_names, argDict)
File "/Users/myuser/Documents/workspace/ilucmc/coremcs/Distro.py", line 150, in __init__
self.dim_indxs = list(starmap(self._convertIndex, enumerate(dim_names))) # convert to numeric values and save in dim_indxs
File "/Users/myuser/Documents/workspace/ilucmc/coremcs/Distro.py", line 194, in _convertIndex
print self.param.sets[dimInd], ' is the param at dimind'
IndexError: list index out of range
Obviously this isn't the code for the whole class, but it represents something that I don't understand. The error is coming when I index into self.param.sets. Apparently, dimInd is out of range. the problem is, dimInd is 0, and self.param.sets is a list of length 1 (as shown from the print statements), so why can't I index into it?
EDIT: For what it's worth, the __init__ method looks like this:
'''
Stores a definitions of a distribution to be applied to a header variable.
See the file setup/gtap/DistroDoc.txt for the details.
'''
def __init__(self, param, target, distType, dim_names, argDict):
self.name = param.name
self.dim_names = dim_names
self.dim_indxs = []
self.target = target.lower() if target else None
self.distType = distType.lower() if distType else None
self.rv = None
self.argDict = {}
self.modifier = defaultdict(lambda: None)
self.param = param
# Separate args into modifiers and distribution arguments
for k, v in argDict.iteritems():
if k[0] == '_': # modifiers start with underscore
self.modifier[k] = v
else:
self.argDict[k] = v # distribution arguments do not have underscore
if self.target == 'index':
print dim_names
self.dim_indxs = list(starmap(self._convertIndex, enumerate(dim_names))) # convert to numeric values and save in dim_indxs
if distType == 'discrete':
entries = self.modifier['_entries']
if not entries:
raise DistributionSpecError("Not enough arguments given to discrete distribution.")
modDict = {k[1:]: float(v) for k, v in self.modifier.iteritems() if k[1:] in getOptionalArgs(DiscreteDist.__init__)}
self.rv = DiscreteDist(entries, **modDict)
return
sig = DistroGen.signature(distType, self.argDict.keys())
gen = DistroGen.generator(sig)
if gen is None:
raise DistributionSpecError("Unknown distribution signature %s" % str(sig))
self.rv = gen.makeRV(self.argDict) # generate a frozen RV with the specified arguments
self.isFactor = gen.isFactor

Related

Stuck on Python "KeyError: <exception str() failed>" in BFS code of a water jug scenario

Intended Function of code: Takes a user input for the volume of 3 jars(1-9) and output the volumes with one of the jars containing the target length. jars can be Emptied/Filled a jar, or poured from one jar to another until one is empty or full.
With the code I have, i'm stuck on a key exception error .
Target length is 4 for this case
Code:
`
class Graph:
class GraphNode:
def __init__(self, jar1 = 0, jar2 = 0, jar3 = 0, color = "white", pi = None):
self.jar1 = jar1
self.jar2 = jar2
self.jar3 = jar3
self.color = color
self.pi = pi
def __repr__(self):
return str(self)
def __init__(self, jl1 = 0, jl2 = 0, jl3 = 0, target = 0):
self.jl1 = jl1
self.jl2 = jl2
self.jl3 = jl3
self.target = target
self.V = {}
for x in range(jl1 + 1):
for y in range(jl2 + 1):
for z in range(jl3 + 1):
node = Graph.GraphNode(x, y, z, "white", None)
self.V[node] = None
def isFound(self, a: GraphNode) -> bool:
if self.target in [a.jar1, a.jar2, a.jar3]:
return True
return False
pass
def isAdjacent(self, a: GraphNode, b: GraphNode) -> bool:
if self.V[a]==b:
return True
return False
pass
def BFS(self) -> [] :
start = Graph.GraphNode(0, 0, 0, "white")
queue=[]
queue.append(start)
while len(queue)>0:
u=queue.pop(0)
for v in self.V:
if self.isAdjacent(u,v):
if v.color =="white":
v.color == "gray"
v.pi=u
if self.isFound(v):
output=[]
while v.pi is not None:
output.insert(0,v)
v=v.pi
return output
else:
queue.append(v)
u.color="black"
return []
#######################################################
j1 = input("Size of first jar: ")
j2 = input("Size of second jar: ")
j3 = input("Size of third jar: ")
t = input("Size of target: ")
jar1 = int(j1)
jar2 = int(j2)
jar3 = int(j3)
target = int(t)
graph1 = Graph(jar1, jar2, jar3, target)
output = graph1.BFS()
print(output)
`
**Error: **
line 37, in isAdjacent
if self.V[a]==b:
KeyError: <exception str() failed>
Strange but when I first ran this in the IPython interpreter I got a different exception:
... :35, in Graph.isAdjacent(self, a, b)
34 def isAdjacent(self, a: GraphNode, b: GraphNode) -> bool:
---> 35 if self.V[a]==b:
36 return True
37 return False
<class 'str'>: (<class 'RecursionError'>, RecursionError('maximum recursion depth exceeded while getting the str of an object'))
When I run it as a script or in the normal interpreter I do get the same one you had:
... line 35, in isAdjacent
if self.V[a]==b:
KeyError: <exception str() failed>
I'm not sure what this means so I ran the debugger and got this:
File "/Users/.../stackoverflow/bfs1.py", line 1, in <module>
class Graph:
File "/Users/.../stackoverflow/bfs1.py", line 47, in BFS
if self.isAdjacent(u,v):
File "/Users/.../stackoverflow/bfs1.py", line 35, in isAdjacent
if self.V[a]==b:
KeyError: <unprintable KeyError object>
Uncaught exception. Entering post mortem debugging
Running 'cont' or 'step' will restart the program
> /Users/.../stackoverflow/bfs1.py(35)isAdjacent()
-> if self.V[a]==b:
(Pdb) type(a)
<class '__main__.Graph.GraphNode'>
(Pdb) str(a)
*** RecursionError: maximum recursion depth exceeded while calling a Python object
So it does seem like a maximum recursion error. (The error message you originally got is not very helpful). But the words <unprintable KeyError object> are a clue. It looks like it was not able to display the KeyError exception...
The culprit is this line in your class definition:
def __repr__(self):
return str(self)
What were you trying to do here?
The __repr__ function is called when the class is asked to produce a string representation of itself. But yours calls the string function on the instance of the class so it will call itself! So I think you actually generated a second exception while the debugger was trying to display the first!!!.
I replaced these lines with
def __repr__(self):
return f"GraphNode({self.jar1}, {self.jar2}, {self.jar3}, {self.color}, {self.pi})"
and I don't get the exception now:
Size of first jar: 1
Size of second jar: 3
Size of third jar: 6
Size of target: 4
Traceback (most recent call last):
File "/Users/.../stackoverflow/bfs1.py", line 77, in <module>
output = graph1.BFS()
File "/Users/.../stackoverflow/bfs1.py", line 45, in BFS
if self.isAdjacent(u,v):
File "/Users/.../stackoverflow/bfs1.py", line 33, in isAdjacent
if self.V[a]==b:
KeyError: GraphNode(0, 0, 0, white, None)
This exception is easier to interpret. Now it's over to you to figure out why this GraphNode was not found in the keys of self.V!

cant download with pytube even with version 12

I have seen other posts about this error, and I have applied all the recommended so-called 'fixes', but none of them are working in my case.
I have windows 11, AMD 5700g, Radeon Rx 560, 32gb RAM.
So here's the error text in full:
Traceback (most recent call last):
File "C:\Users\Redje\AppData\Local\Programs\Python\Python310\lib\site-packages\pytube\__main__.py", line 181, in fmt_streams
extract.apply_signature(stream_manifest, self.vid_info, self.js)
File "C:\Users\Redje\AppData\Local\Programs\Python\Python310\lib\site-packages\pytube\extract.py", line 409, in apply_signature
cipher = Cipher(js=js)
File "C:\Users\Redje\AppData\Local\Programs\Python\Python310\lib\site-packages\pytube\cipher.py", line 43, in __init__
self.throttling_plan = get_throttling_plan(js)
File "C:\Users\Redje\AppData\Local\Programs\Python\Python310\lib\site-packages\pytube\cipher.py", line 405, in get_throttling_plan
raw_code = get_throttling_function_code(js)
File "C:\Users\Redje\AppData\Local\Programs\Python\Python310\lib\site-packages\pytube\cipher.py", line 311, in get_throttling_function_code
name = re.escape(get_throttling_function_name(js))
File "C:\Users\Redje\AppData\Local\Programs\Python\Python310\lib\site-packages\pytube\cipher.py", line 296, in get_throttling_function_name
raise RegexMatchError(
pytube.exceptions.RegexMatchError: get_throttling_function_name: could not find match for multiple
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "C:\Users\Redje\Desktop\PythonFiles\YouTubeDownloader\YTD1.py", line 5, in <module>
stream = video.streams.get_highest_resolution()
File "C:\Users\Redje\AppData\Local\Programs\Python\Python310\lib\site-packages\pytube\__main__.py", line 296, in streams
return StreamQuery(self.fmt_streams)
File "C:\Users\Redje\AppData\Local\Programs\Python\Python310\lib\site-packages\pytube\__main__.py", line 188, in fmt_streams
extract.apply_signature(stream_manifest, self.vid_info, self.js)
File "C:\Users\Redje\AppData\Local\Programs\Python\Python310\lib\site-packages\pytube\extract.py", line 409, in apply_signature
cipher = Cipher(js=js)
File "C:\Users\Redje\AppData\Local\Programs\Python\Python310\lib\site-packages\pytube\cipher.py", line 43, in __init__
self.throttling_plan = get_throttling_plan(js)
File "C:\Users\Redje\AppData\Local\Programs\Python\Python310\lib\site-packages\pytube\cipher.py", line 405, in get_throttling_plan
raw_code = get_throttling_function_code(js)
File "C:\Users\Redje\AppData\Local\Programs\Python\Python310\lib\site-packages\pytube\cipher.py", line 311, in get_throttling_function_code
name = re.escape(get_throttling_function_name(js))
File "C:\Users\Redje\AppData\Local\Programs\Python\Python310\lib\site-packages\pytube\cipher.py", line 296, in get_throttling_function_name
raise RegexMatchError(
pytube.exceptions.RegexMatchError: get_throttling_function_name: could not find match for multiple
I have tweaked 'cipher.py' according to an earlier post, and no luck. Any help would be appreciated.
Here is my cipher.py code:
"""
This module contains all logic necessary to decipher the signature.
YouTube's strategy to restrict downloading videos is to send a ciphered version
of the signature to the client, along with the decryption algorithm obfuscated
in JavaScript. For the clients to play the videos, JavaScript must take the
ciphered version, cycle it through a series of "transform functions," and then
signs the media URL with the output.
This module is responsible for (1) finding and extracting those "transform
functions" (2) maps them to Python equivalents and (3) taking the ciphered
signature and decoding it.
"""
import logging
import re
from itertools import chain
from typing import Any, Callable, Dict, List, Optional, Tuple
from pytube.exceptions import ExtractError, RegexMatchError
from pytube.helpers import cache, regex_search
from pytube.parser import find_object_from_startpoint, throttling_array_split
logger = logging.getLogger(__name__)
class Cipher:
def __init__(self, js: str):
self.transform_plan: List[str] = get_transform_plan(js)
var_regex = re.compile(r"^\w+\W")
var_match = var_regex.search(self.transform_plan[0])
if not var_match:
raise RegexMatchError(
caller="__init__", pattern=var_regex.pattern
)
var = var_match.group(0)[:-1]
self.transform_map = get_transform_map(js, var)
self.js_func_patterns = [
r"\w+\.(\w+)\(\w,(\d+)\)",
r"\w+\[(\"\w+\")\]\(\w,(\d+)\)"
]
self.throttling_plan = get_throttling_plan(js)
self.throttling_array = get_throttling_function_array(js)
self.calculated_n = None
def calculate_n(self, initial_n: list):
"""Converts n to the correct value to prevent throttling."""
if self.calculated_n:
return self.calculated_n
# First, update all instances of 'b' with the list(initial_n)
for i in range(len(self.throttling_array)):
if self.throttling_array[i] == 'b':
self.throttling_array[i] = initial_n
for step in self.throttling_plan:
curr_func = self.throttling_array[int(step[0])]
if not callable(curr_func):
logger.debug(f'{curr_func} is not callable.')
logger.debug(f'Throttling array:\n{self.throttling_array}\n')
raise ExtractError(f'{curr_func} is not callable.')
first_arg = self.throttling_array[int(step[1])]
if len(step) == 2:
curr_func(first_arg)
elif len(step) == 3:
second_arg = self.throttling_array[int(step[2])]
curr_func(first_arg, second_arg)
self.calculated_n = ''.join(initial_n)
return self.calculated_n
def get_signature(self, ciphered_signature: str) -> str:
"""Decipher the signature.
Taking the ciphered signature, applies the transform functions.
:param str ciphered_signature:
The ciphered signature sent in the ``player_config``.
:rtype: str
:returns:
Decrypted signature required to download the media content.
"""
signature = list(ciphered_signature)
for js_func in self.transform_plan:
name, argument = self.parse_function(js_func) # type: ignore
signature = self.transform_map[name](signature, argument)
logger.debug(
"applied transform function\n"
"output: %s\n"
"js_function: %s\n"
"argument: %d\n"
"function: %s",
"".join(signature),
name,
argument,
self.transform_map[name],
)
return "".join(signature)
#cache
def parse_function(self, js_func: str) -> Tuple[str, int]:
"""Parse the Javascript transform function.
Break a JavaScript transform function down into a two element ``tuple``
containing the function name and some integer-based argument.
:param str js_func:
The JavaScript version of the transform function.
:rtype: tuple
:returns:
two element tuple containing the function name and an argument.
**Example**:
parse_function('DE.AJ(a,15)')
('AJ', 15)
"""
logger.debug("parsing transform function")
for pattern in self.js_func_patterns:
regex = re.compile(pattern)
parse_match = regex.search(js_func)
if parse_match:
fn_name, fn_arg = parse_match.groups()
return fn_name, int(fn_arg)
raise RegexMatchError(
caller="parse_function", pattern="js_func_patterns"
)
def get_initial_function_name(js: str) -> str:
"""Extract the name of the function responsible for computing the signature.
:param str js:
The contents of the base.js asset file.
:rtype: str
:returns:
Function name from regex match
"""
function_patterns = [
r"\b[cs]\s*&&\s*[adf]\.set\([^,]+\s*,\s*encodeURIComponent\s*\(\s*(?P<sig>[a-zA-Z0-9$]+)\(", # noqa: E501
r"\b[a-zA-Z0-9]+\s*&&\s*[a-zA-Z0-9]+\.set\([^,]+\s*,\s*encodeURIComponent\s*\(\s*(?P<sig>[a-zA-Z0-9$]+)\(", # noqa: E501
r'(?:\b|[^a-zA-Z0-9$])(?P<sig>[a-zA-Z0-9$]{2})\s*=\s*function\(\s*a\s*\)\s*{\s*a\s*=\s*a\.split\(\s*""\s*\)', # noqa: E501
r'(?P<sig>[a-zA-Z0-9$]+)\s*=\s*function\(\s*a\s*\)\s*{\s*a\s*=\s*a\.split\(\s*""\s*\)', # noqa: E501
r'(["\'])signature\1\s*,\s*(?P<sig>[a-zA-Z0-9$]+)\(',
r"\.sig\|\|(?P<sig>[a-zA-Z0-9$]+)\(",
r"yt\.akamaized\.net/\)\s*\|\|\s*.*?\s*[cs]\s*&&\s*[adf]\.set\([^,]+\s*,\s*(?:encodeURIComponent\s*\()?\s*(?P<sig>[a-zA-Z0-9$]+)\(", # noqa: E501
r"\b[cs]\s*&&\s*[adf]\.set\([^,]+\s*,\s*(?P<sig>[a-zA-Z0-9$]+)\(", # noqa: E501
r"\b[a-zA-Z0-9]+\s*&&\s*[a-zA-Z0-9]+\.set\([^,]+\s*,\s*(?P<sig>[a-zA-Z0-9$]+)\(", # noqa: E501
r"\bc\s*&&\s*a\.set\([^,]+\s*,\s*\([^)]*\)\s*\(\s*(?P<sig>[a-zA-Z0-9$]+)\(", # noqa: E501
r"\bc\s*&&\s*[a-zA-Z0-9]+\.set\([^,]+\s*,\s*\([^)]*\)\s*\(\s*(?P<sig>[a-zA-Z0-9$]+)\(", # noqa: E501
r"\bc\s*&&\s*[a-zA-Z0-9]+\.set\([^,]+\s*,\s*\([^)]*\)\s*\(\s*(?P<sig>[a-zA-Z0-9$]+)\(", # noqa: E501
]
logger.debug("finding initial function name")
for pattern in function_patterns:
regex = re.compile(pattern)
function_match = regex.search(js)
if function_match:
logger.debug("finished regex search, matched: %s", pattern)
return function_match.group(1)
raise RegexMatchError(
caller="get_initial_function_name", pattern="multiple"
)
def get_transform_plan(js: str) -> List[str]:
"""Extract the "transform plan".
The "transform plan" is the functions that the ciphered signature is
cycled through to obtain the actual signature.
:param str js:
The contents of the base.js asset file.
**Example**:
['DE.AJ(a,15)',
'DE.VR(a,3)',
'DE.AJ(a,51)',
'DE.VR(a,3)',
'DE.kT(a,51)',
'DE.kT(a,8)',
'DE.VR(a,3)',
'DE.kT(a,21)']
"""
name = re.escape(get_initial_function_name(js))
pattern = r"%s=function\(\w\){[a-z=\.\(\"\)]*;(.*);(?:.+)}" % name
logger.debug("getting transform plan")
return regex_search(pattern, js, group=1).split(";")
def get_transform_object(js: str, var: str) -> List[str]:
"""Extract the "transform object".
The "transform object" contains the function definitions referenced in the
"transform plan". The ``var`` argument is the obfuscated variable name
which contains these functions, for example, given the function call
``DE.AJ(a,15)`` returned by the transform plan, "DE" would be the var.
:param str js:
The contents of the base.js asset file.
:param str var:
The obfuscated variable name that stores an object with all functions
that descrambles the signature.
**Example**:
>>> get_transform_object(js, 'DE')
['AJ:function(a){a.reverse()}',
'VR:function(a,b){a.splice(0,b)}',
'kT:function(a,b){var c=a[0];a[0]=a[b%a.length];a[b]=c}']
"""
pattern = r"var %s={(.*?)};" % re.escape(var)
logger.debug("getting transform object")
regex = re.compile(pattern, flags=re.DOTALL)
transform_match = regex.search(js)
if not transform_match:
raise RegexMatchError(caller="get_transform_object", pattern=pattern)
return transform_match.group(1).replace("\n", " ").split(", ")
def get_transform_map(js: str, var: str) -> Dict:
"""Build a transform function lookup.
Build a lookup table of obfuscated JavaScript function names to the
Python equivalents.
:param str js:
The contents of the base.js asset file.
:param str var:
The obfuscated variable name that stores an object with all functions
that descrambles the signature.
"""
transform_object = get_transform_object(js, var)
mapper = {}
for obj in transform_object:
# AJ:function(a){a.reverse()} => AJ, function(a){a.reverse()}
name, function = obj.split(":", 1)
fn = map_functions(function)
mapper[name] = fn
return mapper
def get_throttling_function_name(js: str) -> str:
"""Extract the name of the function that computes the throttling parameter.
:param str js:
The contents of the base.js asset file.
:rtype: str
:returns:
The name of the function used to compute the throttling parameter.
"""
function_patterns = [
# https://github.com/ytdl-org/youtube-dl/issues/29326#issuecomment-865985377
# https://github.com/yt-dlp/yt-dlp/commit/48416bc4a8f1d5ff07d5977659cb8ece7640dcd8
# var Bpa = [iha];
# ...
# a.C && (b = a.get("n")) && (b = Bpa[0](b), a.set("n", b),
# Bpa.length || iha("")) }};
# In the above case, `iha` is the relevant function name
r'a\.[a-zA-Z]\s*&&\s*\([a-z]\s*=\s*a\.get\("n"\)\)\s*&&\s*'
r'\([a-z]\s*=\s*([a-zA-Z0-9$]{2, 3})(\[\d+\])?\([a-z]\)',
]
logger.debug('Finding throttling function name')
for pattern in function_patterns:
regex = re.compile(pattern)
function_match = regex.search(js)
if function_match:
logger.debug("finished regex search, matched: %s", pattern)
if len(function_match.groups()) == 1:
return function_match.group(1)
idx = function_match.group(2)
if idx:
idx = idx.strip("[]")
array = re.search(
r'var {nfunc}\s*=\s*(\[.+?\]);'.format(
nfunc=escape(function_match.group(1))),
js
)
if array:
array = array.group(1).strip("[]").split(",")
array = [x.strip() for x in array]
return array[int(idx)]
raise RegexMatchError(
caller="get_throttling_function_name", pattern="multiple"
)
def get_throttling_function_code(js: str) -> str:
"""Extract the raw code for the throttling function.
:param str js:
The contents of the base.js asset file.
:rtype: str
:returns:
The name of the function used to compute the throttling parameter.
"""
# Begin by extracting the correct function name
name = re.escape(get_throttling_function_name(js))
# Identify where the function is defined
pattern_start = r"%s=function\(\w\)" % name
regex = re.compile(pattern_start)
match = regex.search(js)
# Extract the code within curly braces for the function itself, and merge any split lines
code_lines_list = find_object_from_startpoint(js, match.span()[1]).split('\n')
joined_lines = "".join(code_lines_list)
# Prepend function definition (e.g. `Dea=function(a)`)
return match.group(0) + joined_lines
def get_throttling_function_array(js: str) -> List[Any]:
"""Extract the "c" array.
:param str js:
The contents of the base.js asset file.
:returns:
The array of various integers, arrays, and functions.
"""
raw_code = get_throttling_function_code(js)
array_start = r",c=\["
array_regex = re.compile(array_start)
match = array_regex.search(raw_code)
array_raw = find_object_from_startpoint(raw_code, match.span()[1] - 1)
str_array = throttling_array_split(array_raw)
converted_array = []
for el in str_array:
try:
converted_array.append(int(el))
continue
except ValueError:
# Not an integer value.
pass
if el == 'null':
converted_array.append(None)
continue
if el.startswith('"') and el.endswith('"'):
# Convert e.g. '"abcdef"' to string without quotation marks, 'abcdef'
converted_array.append(el[1:-1])
continue
if el.startswith('function'):
mapper = (
(r"{for\(\w=\(\w%\w\.length\+\w\.length\)%\w\.length;\w--;\)\w\.unshift\(\w.pop\(\)\)}", throttling_unshift), # noqa:E501
(r"{\w\.reverse\(\)}", throttling_reverse),
(r"{\w\.push\(\w\)}", throttling_push),
(r";var\s\w=\w\[0\];\w\[0\]=\w\[\w\];\w\[\w\]=\w}", throttling_swap),
(r"case\s\d+", throttling_cipher_function),
(r"\w\.splice\(0,1,\w\.splice\(\w,1,\w\[0\]\)\[0\]\)", throttling_nested_splice), # noqa:E501
(r";\w\.splice\(\w,1\)}", js_splice),
(r"\w\.splice\(-\w\)\.reverse\(\)\.forEach\(function\(\w\){\w\.unshift\(\w\)}\)", throttling_prepend), # noqa:E501
(r"for\(var \w=\w\.length;\w;\)\w\.push\(\w\.splice\(--\w,1\)\[0\]\)}", throttling_reverse), # noqa:E501
)
found = False
for pattern, fn in mapper:
if re.search(pattern, el):
converted_array.append(fn)
found = True
if found:
continue
converted_array.append(el)
# Replace null elements with array itself
for i in range(len(converted_array)):
if converted_array[i] is None:
converted_array[i] = converted_array
return converted_array
def get_throttling_plan(js: str):
"""Extract the "throttling plan".
The "throttling plan" is a list of tuples used for calling functions
in the c array. The first element of the tuple is the index of the
function to call, and any remaining elements of the tuple are arguments
to pass to that function.
:param str js:
The contents of the base.js asset file.
:returns:
The full function code for computing the throttlign parameter.
"""
raw_code = get_throttling_function_code(js)
transform_start = r"try{"
plan_regex = re.compile(transform_start)
match = plan_regex.search(raw_code)
transform_plan_raw = find_object_from_startpoint(raw_code, match.span()[1] - 1)
# Steps are either c[x](c[y]) or c[x](c[y],c[z])
step_start = r"c\[(\d+)\]\(c\[(\d+)\](,c(\[(\d+)\]))?\)"
step_regex = re.compile(step_start)
matches = step_regex.findall(transform_plan_raw)
transform_steps = []
for match in matches:
if match[4] != '':
transform_steps.append((match[0],match[1],match[4]))
else:
transform_steps.append((match[0],match[1]))
return transform_steps
def reverse(arr: List, _: Optional[Any]):
"""Reverse elements in a list.
This function is equivalent to:
.. code-block:: javascript
function(a, b) { a.reverse() }
This method takes an unused ``b`` variable as their transform functions
universally sent two arguments.
**Example**:
>>> reverse([1, 2, 3, 4])
[4, 3, 2, 1]
"""
return arr[::-1]
def splice(arr: List, b: int):
"""Add/remove items to/from a list.
This function is equivalent to:
.. code-block:: javascript
function(a, b) { a.splice(0, b) }
**Example**:
>>> splice([1, 2, 3, 4], 2)
[1, 2]
"""
return arr[b:]
def swap(arr: List, b: int):
"""Swap positions at b modulus the list length.
This function is equivalent to:
.. code-block:: javascript
function(a, b) { var c=a[0];a[0]=a[b%a.length];a[b]=c }
**Example**:
>>> swap([1, 2, 3, 4], 2)
[3, 2, 1, 4]
"""
r = b % len(arr)
return list(chain([arr[r]], arr[1:r], [arr[0]], arr[r + 1 :]))
def throttling_reverse(arr: list):
"""Reverses the input list.
Needs to do an in-place reversal so that the passed list gets changed.
To accomplish this, we create a reversed copy, and then change each
indvidual element.
"""
reverse_copy = arr.copy()[::-1]
for i in range(len(reverse_copy)):
arr[i] = reverse_copy[i]
def throttling_push(d: list, e: Any):
"""Pushes an element onto a list."""
d.append(e)
def throttling_mod_func(d: list, e: int):
"""Perform the modular function from the throttling array functions.
In the javascript, the modular operation is as follows:
e = (e % d.length + d.length) % d.length
We simply translate this to python here.
"""
return (e % len(d) + len(d)) % len(d)
def throttling_unshift(d: list, e: int):
"""Rotates the elements of the list to the right.
In the javascript, the operation is as follows:
for(e=(e%d.length+d.length)%d.length;e--;)d.unshift(d.pop())
"""
e = throttling_mod_func(d, e)
new_arr = d[-e:] + d[:-e]
d.clear()
for el in new_arr:
d.append(el)
def throttling_cipher_function(d: list, e: str):
"""This ciphers d with e to generate a new list.
In the javascript, the operation is as follows:
var h = [A-Za-z0-9-_], f = 96; // simplified from switch-case loop
d.forEach(
function(l,m,n){
this.push(
n[m]=h[
(h.indexOf(l)-h.indexOf(this[m])+m-32+f--)%h.length
]
)
},
e.split("")
)
"""
h = list('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_')
f = 96
# by naming it "this" we can more closely reflect the js
this = list(e)
# This is so we don't run into weirdness with enumerate while
# we change the input list
copied_list = d.copy()
for m, l in enumerate(copied_list):
bracket_val = (h.index(l) - h.index(this[m]) + m - 32 + f) % len(h)
this.append(
h[bracket_val]
)
d[m] = h[bracket_val]
f -= 1
def throttling_nested_splice(d: list, e: int):
"""Nested splice function in throttling js.
In the javascript, the operation is as follows:
function(d,e){
e=(e%d.length+d.length)%d.length;
d.splice(
0,
1,
d.splice(
e,
1,
d[0]
)[0]
)
}
While testing, all this seemed to do is swap element 0 and e,
but the actual process is preserved in case there was an edge
case that was not considered.
"""
e = throttling_mod_func(d, e)
inner_splice = js_splice(
d,
e,
1,
d[0]
)
js_splice(
d,
0,
1,
inner_splice[0]
)
def throttling_prepend(d: list, e: int):
"""
In the javascript, the operation is as follows:
function(d,e){
e=(e%d.length+d.length)%d.length;
d.splice(-e).reverse().forEach(
function(f){
d.unshift(f)
}
)
}
Effectively, this moves the last e elements of d to the beginning.
"""
start_len = len(d)
# First, calculate e
e = throttling_mod_func(d, e)
# Then do the prepending
new_arr = d[-e:] + d[:-e]
# And update the input list
d.clear()
for el in new_arr:
d.append(el)
end_len = len(d)
assert start_len == end_len
def throttling_swap(d: list, e: int):
"""Swap positions of the 0'th and e'th elements in-place."""
e = throttling_mod_func(d, e)
f = d[0]
d[0] = d[e]
d[e] = f
def js_splice(arr: list, start: int, delete_count=None, *items):
"""Implementation of javascript's splice function.
:param list arr:
Array to splice
:param int start:
Index at which to start changing the array
:param int delete_count:
Number of elements to delete from the array
:param *items:
Items to add to the array
Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice # noqa:E501
"""
# Special conditions for start value
try:
if start > len(arr):
start = len(arr)
# If start is negative, count backwards from end
if start < 0:
start = len(arr) - start
except TypeError:
# Non-integer start values are treated as 0 in js
start = 0
# Special condition when delete_count is greater than remaining elements
if not delete_count or delete_count >= len(arr) - start:
delete_count = len(arr) - start # noqa: N806
deleted_elements = arr[start:start + delete_count]
# Splice appropriately.
new_arr = arr[:start] + list(items) + arr[start + delete_count:]
# Replace contents of input array
arr.clear()
for el in new_arr:
arr.append(el)
return deleted_elements
def map_functions(js_func: str) -> Callable:
"""For a given JavaScript transform function, return the Python equivalent.
:param str js_func:
The JavaScript version of the transform function.
"""
mapper = (
# function(a){a.reverse()}
(r"{\w\.reverse\(\)}", reverse),
# function(a,b){a.splice(0,b)}
(r"{\w\.splice\(0,\w\)}", splice),
# function(a,b){var c=a[0];a[0]=a[b%a.length];a[b]=c}
(r"{var\s\w=\w\[0\];\w\[0\]=\w\[\w\%\w.length\];\w\[\w\]=\w}", swap),
# function(a,b){var c=a[0];a[0]=a[b%a.length];a[b%a.length]=c}
(
r"{var\s\w=\w\[0\];\w\[0\]=\w\[\w\%\w.length\];\w\[\w\%\w.length\]=\w}",
swap,
),
)
for pattern, fn in mapper:
if re.search(pattern, js_func):
return fn
raise RegexMatchError(caller="map_functions", pattern="multiple")
Any help would be appreciated!

matplotlib xlim TypeError: '>' not supported between instances of 'int' and 'list'

this is the original repo i'm trying to run in my computer: https://github.com/kreamkorokke/cs244-final-project
import os
import matplotlib.pyplot as plt
import argparse
from attacker import check_attack_type
IMG_DIR = "./plots"
def read_lines(f, d):
lines = f.readlines()[:-1]
for line in lines:
typ, time, num = line.split(',')
if typ == 'seq':
d['seq']['time'].append(float(time))
d['seq']['num'].append(float(num))
elif typ == 'ack':
d['ack']['time'].append(float(time))
d['ack']['num'].append(float(num))
else:
raise "Unknown type read while parsing log file: %s" % typ
def main():
parser = argparse.ArgumentParser(description="Plot script for plotting sequence numbers.")
parser.add_argument('--save', dest='save_imgs', action='store_true',
help="Set this to true to save images under specified output directory.")
parser.add_argument('--attack', dest='attack',
nargs='?', const="", type=check_attack_type,
help="Attack name (used in plot names).")
parser.add_argument('--output', dest='output_dir', default=IMG_DIR,
help="Directory to store plots.")
args = parser.parse_args()
save_imgs = args.save_imgs
output_dir = args.output_dir
attack_name = args.attack
if save_imgs and attack_name not in ['div', 'dup', 'opt'] :
print("Attack name needed for saving plot figures.")
return
normal_log = {'seq':{'time':[], 'num':[]}, 'ack':{'time':[], 'num':[]}}
attack_log = {'seq':{'time':[], 'num':[]}, 'ack':{'time':[], 'num':[]}}
normal_f = open('log.txt', 'r')
attack_f = open('%s_attack_log.txt' % attack_name, 'r')
read_lines(normal_f, normal_log)
read_lines(attack_f, attack_log)
if attack_name == 'div':
attack_desc = 'ACK Division'
elif attack_name == 'dup':
attack_desc = 'DupACK Spoofing'
elif attack_name == 'opt':
attack_desc = 'Optimistic ACKing'
else:
raise 'Unknown attack type: %s' % attack_name
norm_seq_time, norm_seq_num = normal_log['seq']['time'], normal_log['seq']['num']
norm_ack_time, norm_ack_num = normal_log['ack']['time'], normal_log['ack']['num']
atck_seq_time, atck_seq_num = attack_log['seq']['time'], attack_log['seq']['num']
atck_ack_time, atck_ack_num = attack_log['ack']['time'], attack_log['ack']['num']
plt.plot(norm_seq_time, norm_seq_num, 'b^', label='Regular TCP Data Segments')
plt.plot(norm_ack_time, norm_ack_num, 'bx', label='Regular TCP ACKs')
plt.plot(atck_seq_time, atck_seq_num, 'rs', label='%s Attack Data Segments' % attack_desc)
plt.plot(atck_ack_time, atck_ack_num, 'r+', label='%s Attack ACKs' % attack_desc)
plt.legend(loc='upper left')
x = max(max(norm_seq_time, norm_ack_time),max(atck_seq_time, atck_ack_time))
y = max(max(norm_seq_num, norm_ack_num),max(atck_seq_num, atck_ack_num))
plt.xlim(0, x)
plt.ylim(0,y)
plt.xlabel('Time (s)')
plt.ylabel('Sequence Number (Bytes)')
if save_imgs:
# Save images to figure/
if not os.path.exists(output_dir):
os.makedirs(output_dir)
plt.savefig(output_dir + "/" + attack_name)
else:
plt.show()
normal_f.close()
attack_f.close()
if __name__ == "__main__":
main()
after running this i get this error
Traceback (most recent call last):
File "plot.py", line 85, in <module>
main()
File "plot.py", line 66, in main
plt.xlim(0, a)
File "/usr/lib/python3/dist-packages/matplotlib/pyplot.py", line 1427, in xlim
ret = ax.set_xlim(*args, **kwargs)
File "/usr/lib/python3/dist-packages/matplotlib/axes/_base.py", line 3267, in set_xlim
reverse = left > right
TypeError: '>' not supported between instances of 'int' and 'list'
Done! Please check ./plots for all generated plots.
how can i solve this problem? or better yet if there is another way of running this project? i installed matplotlib via pip3 install matplotlib command (same with scapy) and my main python version is python2 right now but i run the project with python3, could the issue be about this? what am i missing? or is it about mininet itself?
The problem is in this line
x = max(max(norm_seq_time, norm_ack_time),max(atck_seq_time, atck_ack_time))
IIUC, you wanna assign to x the maximum value among all those four lists. However, when you pass two lists to the max function, such as max(norm_seq_time, norm_ack_time), it will return the list it considers the greater one, and not the highest value considering both lists.
Instead, you can do something like:
x = max(norm_seq_time + norm_ack_time + atck_seq_time + atck_ack_time)
This will concatenate the four lists into a single one. Then, the function will return the highest value among all of them. You might wanna do that to the calculation of y as well.
If this is not what you wanted, or if you have any further issues, please let us know.
with the help of a friend we solved this problem by changing a part in code into this:
max_norm_seq_time = max(norm_seq_time) if len(norm_seq_time) > 0 else 0
max_norm_ack_time = max(norm_ack_time) if len(norm_ack_time) > 0 else 0
max_atck_seq_time = max(atck_seq_time) if len(atck_seq_time) > 0 else 0
max_atck_ack_time = max(atck_ack_time) if len(atck_ack_time) > 0 else 0
x = max((max_norm_seq_time, max_norm_ack_time,\
max_atck_seq_time, max_atck_ack_time))
plt.xlim([0,x])
max_norm_seq_num = max(norm_seq_num) if len(norm_seq_num) > 0 else 0
max_norm_ack_num = max(norm_ack_num) if len(norm_ack_num) > 0 else 0
max_atck_seq_num = max(atck_seq_num) if len(atck_seq_num) > 0 else 0
max_atck_ack_num = max(atck_ack_num) if len(atck_ack_num) > 0 else 0
plt.ylim([0, max((max_norm_seq_num, max_norm_ack_num,\
max_atck_seq_num, max_atck_ack_num))])
```
writing here just in case anyone else needs it.

AttributeError: 'bool' object has no attribute 'items'

I'm a beginner in python. I'm currently try to deal with use the IK to move the robot arm. When I try to run my program the arm was able to move to the my setted starting position but when it's going to next step it shows me this error:AttributeError: 'bool' object has no attribute 'items'
This is my program:
class Pick_Place (object):
#def __init__(self,limb,hover_distance = 0.15):
def __init__(self,limb):
self._limb = baxter_interface.Limb(limb)
self._gripper = baxter_interface.Gripper(limb)
self._gripper.calibrate(limb)
#self.gripper_open()
#self._verbose = verbose
ns = "ExternalTools/" + limb + "/PositionKinematicsNode/IKService"
self._iksvc = rospy.ServiceProxy(ns,SolvePositionIK)
rospy.wait_for_service(ns, 5.0)
def move_to_start (self,start_angles = None):
print ("moving.....")
if not start_angles:
print ("it is 0")
start_angles = dict(zip(self._joint_names, [0]*7))
self._guarded_move_to_joint_position(start_angles)
self.gripper_open()
rospy.sleep(1.0)
print ("moved!!!")
#########################IK_Server################################################
def ik_request (self,pose):
hdr = Header(stamp=rospy.Time.now(),frame_id='base')
ikreq = SolvePositionIKRequest()
ikreq.pose_stamp.append(PoseStamped(header=hdr, pose=pose))
try:
resp = self._iksvc (ikreq)
except (rospy.ServiceException, rospy.ROSException), e:
rospy.logerr("Service call failed: %s" % (e,))
return False
limb_joints = {}
limb_joints = dict(zip(resp.joints[0].name, resp.joints[0].position))
return limb_joints
###################################################################################
def _guarded_move_to_joint_position(self,joint_angles):
print ("joint position.....")
self._limb.move_to_joint_positions(joint_angles)
def gripper_open (self):
self._gripper.open()
rospy.sleep(1.0)
def gripper_close (self):
self._gripper.close()
rospy.sleep(1.0)
#################################Individual_Motion####################################
def _approach (self, pose):
print ("\nApproaching.....")
approach = copy.deepcopy(pose)
approach.position.z = approach.position.z #+ self._hover_distance
joint_angles = self.ik_request(approach)
self._guarded_move_to_joint_position(joint_angles)
print ("\nApproached.....")
def _retract (self):
print ("\nRetracting.....")
current_pose = self._limb.endpoint_pose()
ik_pose = Pose()
ik_pose.position.x = current_pose['position'].x
ik_pose.position.y = current_pose['position'].y
ik_pose.position.z = current_pose['position'].z #+ self._hover_distance
ik_pose.orientation.x = current_pose['orientation'].x
ik_pose.orientation.y = current_pose['orientation'].y
ik_pose.orientation.z = current_pose['orientation'].z
ik_pose.orientation.w = current_pose['orientation'].w
joint_angles = self.ik_request(ik_pose)
self._guarded_move_to_joint_position(joint_angles)
print ("\nRetracted......")
def _servo_to_pose (self, pose):
print ("\nPosing.....")
joint_angles = self.ik_request(pose)
self._guarded_move_to_joint_position(joint_angles)
print ("\nPosed.....")
##########################Motion_of_pick_and_place#####################################
def pick (self,pose):
print ("\nPicking_1.....")
# open the gripper
self.gripper_open()
# servo above pose
self._approach(pose)
# servo to pose
self._servo_to_pose(pose)
# close gripper
self.gripper_close()
# retract to clear object
self._retract()
print ("\nPicked")
def place (self,pose):
print ("\nPlacing_1.....")
# servo above pose
self._approach(pose)
# servo to pose
self._servo_to_pose(pose)
# open the gripper
self.gripper_open()
# retract to clear object
self._retract()
print ("\nPlaced")
###########################Main_Program############################################
def main():
print ("Initializing....")
rospy.init_node("ylj_ik_traTest")
print("Getting the robot state.....")
rs= baxter_interface.RobotEnable()
print ("Enabling....")
rs.enable()
limb = 'left'
#hover_distance = 0.15
starting_joint_angles = {'left_s0': -0.50,
'left_s1': -1.30,
'left_e0': -0.60,
'left_e1': 1.30,
'left_w0': 0.20,
'left_w1': 1.60,
'left_w2': -0.30}
#pnp = Pick_Place(limb,hover_distance)
pnp = Pick_Place(limb)
overhead_orientation = Quaternion(
x=-0.0249590815779,
y=0.999649402929,
z=0.00737916180073,
w=0.00486450832011)
ball_poses = list()
#1st ball point
ball_poses.append(Pose(
position = Point(x=0.7, y=0.15, z=-0.1),
orientation = overhead_orientation))
#2nd ball point
ball_poses.append(Pose(
position = Point(x=0.75, y=0.0, z=-0.1),
orientation = overhead_orientation))
pnp.move_to_start(starting_joint_angles)
idx = 0
while not rospy.is_shutdown():
print ("\nPicking.....")
pnp.pick(ball_poses[idx])
print ("\nPlacing.....")
idx = (idx+1) % len(ball_poses)#?????
pnp.place(ball_poses[idx])
return 0
if __name__ == '__main__':
sys.exit(main())
And this is the error shows me:
Initializing....
Getting the robot state.....
Enabling....
[INFO] [WallTime: 1466477391.621678] Robot Enabled
moving.....
joint position.....
moved!!!
Picking.....
Picking_1.....
Approaching.....
joint position.....
Traceback (most recent call last):
File "/home/baxter/ros_ws/src/baxter_examples/scripts/ylj_research/ylj_ik_traTest.py", line 197, in <module>
sys.exit(main())
File "/home/baxter/ros_ws/src/baxter_examples/scripts/ylj_research/ylj_ik_traTest.py", line 188, in main
pnp.pick(ball_poses[idx])
File "/home/baxter/ros_ws/src/baxter_examples/scripts/ylj_research/ylj_ik_traTest.py", line 122, in pick
self._approach(pose)
File "/home/baxter/ros_ws/src/baxter_examples/scripts/ylj_research/ylj_ik_traTest.py", line 88, in _approach
self._guarded_move_to_joint_position(joint_angles)
File "/home/baxter/ros_ws/src/baxter_examples/scripts/ylj_research/ylj_ik_traTest.py", line 72, in _guarded_move_to_joint_position
self._limb.move_to_joint_positions(joint_angles)
File "/home/baxter/ros_ws/src/baxter_interface/src/baxter_interface/limb.py", line 368, in move_to_joint_positions
diffs = [genf(j, a) for j, a in positions.items() if
AttributeError: 'bool' object has no attribute 'items'
Does anyone had met this kind of error before? Please help me, thank you.
The error tells you that booleans (either True or False) don't have an attribute "items".
When you call
self._limb.move_to_joint_positions(joint_angles) you are passing the argument "joint_angles" which becomes "positions" in the function
move_to_joint_positions().
Looking into the source code of the library you're using, it tells you what it wants positions to be:
#type positions: dict({str:float})
In short, it wants joint_angles to be a dictionary mapping strings to floats and you passed a boolean. Let's look into how you got joint_angles:
joint_angles = self.ik_request(ik_pose)
In the body of your method, you return False every time:
def ik_request (self,pose):
...
except (rospy.ServiceException, rospy.ROSException), e:
rospy.logerr("Service call failed: %s" % (e,))
return False
Returning a boolean is clearly not what you want to do, and it is the cause of this error.
You're trying to access the elements of a boolean value, which is not allowed. In your example, positions is a true/false value and not what you're expecting it to be. There's some section of the code where positions is being assigned to a boolean. You should walk through your code and look for any line that contains positions = something.

Using the Dragonly package for NatSpeak, how do I define a CompoundRule that is both a standalone rule and can also be used by other CompoundRules?

I am working on a module for vim. Currently I have the following code:
alphabet_rule = Sequence([Repetition(RuleRef(name="x", rule=MappingRule(name="t", mapping=ALPHABET)), min=1, max=20)])
numbers_rule = Sequence([Repetition(RuleRef(name="y", rule=MappingRule(name="u", mapping=DIGITS)), min=1, max=20)])
symbols_rule = Sequence([Repetition(RuleRef(name="z", rule=MappingRule(name="v", mapping=SYMBOLS)), min=1, max=20)])
alphanumeric = [alphabet_rule, numbers_rule, symbols_rule]
class FindRule(CompoundRule):
spec = ("(bind | find | tank | bank) <alphanumeric> [<n>]")
extras = [IntegerRef("n", 1, 10), Alternative(alphanumeric, name="alphanumeric")]
defaults = {"n": 1}
def value(self, node, extras):
words = node.words()
rule = words[0]
times = extras["n"]
print words
print "Times: %d" % times
find = Events('key->key=%d;key->key=f' % times)
bind = Events('key->key=%d;key->key=f&modifier=shift' % times)
bank = Events('key->key=%d;key->key=t&modifier=shift' % times)
tank = Events('key->key=%d;key->key=t' % times)
search = extras["alphanumeric"][0][0]
if rule == 'bind':
return (bind + search)
elif rule == 'find':
return (find + search)
elif rule == 'bank':
return (bank + search)
elif rule == 'tank':
return (tank + search)
def _process_recognition(self, node, extras):
self.value(node, extras).execute()
find_rule = RuleRef(name="find_rule", rule=FindRule(name="i"))
class ClipRule(CompoundRule):
spec = ("(clip | dip) <find_rule>")
extras = [find_rule]
def _process_recognition(self, node, extras):
words = node.words()
if words[0] == 'clip':
action = Events('key->key=c')
elif words[0] == 'dip':
action = Events('key->key=d')
(action + extras["find_rule"]).execute()
The code is here to give an idea of what I am trying to do, since it's not runnable w/out all the bells and whistles of dragonfly and some of the ALPHABETS, DIGITS, and SYMBOLS that I've defined.
I can use the FindRule just as I expect, but when I attempt to use the ClipRule, I get the following error:
Traceback (most recent call last):
File "C:\Python27\lib\site-packages\dragonfly\engines\engine_natlink.py", line 248, in results_callback
r.process_recognition(root)
File "C:\Python27\lib\site-packages\dragonfly\grammar\rule_compound.py", line 143, in process_recognition
self._process_recognition(node, extras)
File "C:\NatLink\NatLink\MacroSystem\_vim.py", line 232, in _process_recognition
(action + extras["find_rule"]).execute()
File "C:\Python27\lib\site-packages\dragonfly\actions\action_base.py", line 84, in __add__
copy.append(other)
File "C:\Python27\lib\site-packages\dragonfly\actions\action_base.py", line 79, in append
assert isinstance(other, ActionBase)
AssertionError
When I do => print extras['find_rule'] in ClipRule._process_recognition, I get 'None'. I assume this means that I have incorrectly overridden the value method in FindRule. Wondering if anyone else has had some experience with this. Thanks

Categories

Resources