You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
658 lines
24 KiB
658 lines
24 KiB
import os
|
|
import sys
|
|
import traceback
|
|
import copy
|
|
|
|
verbose_enable = False
|
|
|
|
os_name = "GNU/Linux"
|
|
if os.sep=="\\":
|
|
os_name = "windows"
|
|
print("Windows detected")
|
|
#TODO: deprecate os_name--still needed for detecting whether to use squote in singleimage.py (when not Windows, squote is "'" otherwise "")
|
|
|
|
#formerly pcttext:
|
|
#uppercase_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
#lowercase_chars = uppercase_chars.lower()
|
|
#letter_chars = uppercase_chars+lowercase_chars
|
|
digit_chars = "0123456789"
|
|
#identifier_chars = letter_chars+"_"+digit_chars
|
|
#identifier_and_dot_chars = identifier_chars + "."
|
|
|
|
#formerly from expertmmregressionsuite:
|
|
alpha_upper_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
alpha_lower_chars = alpha_upper_chars.lower()
|
|
alpha_chars = alpha_upper_chars+alpha_lower_chars
|
|
#numeric_chars = "1234567890"
|
|
alnum_chars = alpha_chars+digit_chars
|
|
identifier_chars = alnum_chars+"_"
|
|
identifier_and_dot_chars = identifier_chars+"."
|
|
min_indent = ""
|
|
dict_entries_modified_count = 0
|
|
|
|
class InstalledFile:
|
|
source_dir_path = None
|
|
dest_dir_path = None
|
|
file_name = None
|
|
|
|
def __init__(self, file_name, source_dir_path, dest_dir_path):
|
|
self.file_name=file_name
|
|
self.source_dir_path=source_dir_path
|
|
self.dest_dir_path=dest_dir_path
|
|
|
|
|
|
|
|
class ConfigManager:
|
|
#config_name = None
|
|
_config_path = None
|
|
_data = None
|
|
_ao = None
|
|
|
|
#DOES load variables if path exists
|
|
def __init__(self, config_file_path, assignment_operator_string):
|
|
self._data = {}
|
|
self._config_path = config_file_path
|
|
self._ao = assignment_operator_string
|
|
self._data = get_dict_modified_by_conf_file(self._data, self._config_path, self._ao)
|
|
|
|
#DOES ask for user input if does not exist. If default_value is none, do not add to _data if not given
|
|
def load_var_or_ask_console_input(self, name, default_value, description):
|
|
is_changed = False
|
|
if name not in self._data:
|
|
print("")
|
|
if default_value is None:
|
|
print("WARNING: this program does not have a default value for "+name+".")
|
|
default_value = ""
|
|
answer = raw_input("Please enter "+description+" ("+name+") [blank for "+default_value+"]: ")
|
|
if answer is not None:
|
|
answer = answer.strip()
|
|
|
|
if answer is not None and len(answer)>0:
|
|
self._data[name] = answer
|
|
else:
|
|
self._data[name] = default_value
|
|
print("Using "+name+" '"+self._data[name]+"'")
|
|
is_changed = True
|
|
|
|
if not os.path.isfile(self._config_path):
|
|
is_changed = True
|
|
print("Creating '"+self._config_path+"'")
|
|
if is_changed:
|
|
self.save_yaml()
|
|
|
|
def prepare_var(self, name, default_value, description):
|
|
self.load_var_or_ask_console_input(name, default_value, description)
|
|
|
|
def contains(self, name):
|
|
return (name in self._data.keys())
|
|
|
|
def remove_var(self, name):
|
|
try:
|
|
del self._data[name]
|
|
self.save_yaml()
|
|
except KeyError:
|
|
pass
|
|
|
|
#DOES autosave IF different val
|
|
def set_var(self, name, val):
|
|
is_changed = False
|
|
if name not in self._data.keys():
|
|
print("WARNING to developer: run prepare_var before set_val, so that variable has a default.")
|
|
is_changed = True
|
|
elif self._data[name] != val:
|
|
is_changed = True
|
|
if is_changed:
|
|
self._data[name] = val
|
|
self.save_yaml()
|
|
|
|
def get_var(self, name):
|
|
result = None
|
|
if name in self._data:
|
|
result = self._data[name]
|
|
return result
|
|
|
|
def save_yaml(self):
|
|
save_conf_from_dict(self._config_path, self._data, self._ao, save_nulls_enable=False)
|
|
|
|
def get_dict_deepcopy(old_dict):
|
|
new_dict = None
|
|
if type(old_dict) is dict:
|
|
new_dict = {}
|
|
for this_key in old_dict.iterkeys():
|
|
new_dict[this_key] = copy.deepcopy(old_dict[this_key])
|
|
return new_dict
|
|
|
|
def is_dict_subset(new_dict, old_dict, verbose_messages_enable, verbose_dest_description="unknown file"):
|
|
is_changed = False
|
|
try:
|
|
if old_dict is not None:
|
|
if new_dict is not None:
|
|
old_dict_keys = old_dict.keys()
|
|
for this_key in new_dict.iterkeys():
|
|
if (this_key not in old_dict_keys):
|
|
is_changed = True
|
|
if verbose_messages_enable:
|
|
print("SAVING '"+verbose_dest_description+"' since "+str(this_key)+" not in saved version.")
|
|
break
|
|
elif new_dict[this_key] != old_dict[this_key]:
|
|
is_changed = True
|
|
if verbose_messages_enable:
|
|
print("SAVING '"+verbose_dest_description+"' since "+str(this_key)+" not same as saved version.")
|
|
break
|
|
#else new_dict is None so no change detected (no new information)
|
|
else:
|
|
if new_dict is not None:
|
|
is_changed = True
|
|
except:
|
|
print("Could not finish is_dict_subset:")
|
|
view_traceback()
|
|
return is_changed
|
|
|
|
def vec2_not_in(this_vec, this_list):
|
|
result = False
|
|
if this_list is not None and this_vec is not None:
|
|
for try_vec in this_list:
|
|
if try_vec[0]==this_vec[0] and try_vec[1]==this_vec[1]:
|
|
result = True
|
|
break
|
|
return result
|
|
|
|
def ivec2_equals(pos1, pos2):
|
|
return (int(pos1[0])==int(pos2[0])) and (int(pos1[1])==int(pos2[1]))
|
|
|
|
def get_dict_from_conf_file(path,assignment_operator="=",comment_delimiter="#",inline_comments_enable=False):
|
|
results = None
|
|
results = get_dict_modified_by_conf_file(results, path, assignment_operator, comment_delimiter=comment_delimiter, inline_comments_enable=inline_comments_enable)
|
|
return results
|
|
|
|
def RepresentsInt(s):
|
|
try:
|
|
int(s)
|
|
return True
|
|
except ValueError:
|
|
return False
|
|
|
|
def RepresentsFloat(s):
|
|
try:
|
|
float(s)
|
|
return True
|
|
except ValueError:
|
|
return False
|
|
|
|
def view_traceback():
|
|
ex_type, ex, tb = sys.exc_info()
|
|
print(min_indent+str(ex_type))
|
|
print(min_indent+str(ex))
|
|
traceback.print_tb(tb)
|
|
del tb
|
|
|
|
def print_file(path, min_indent=""):
|
|
line_count = 0
|
|
try:
|
|
if path is not None:
|
|
if os.path.isfile(path):
|
|
|
|
if min_indent is None:
|
|
min_indent = ""
|
|
ins = open(path, 'r')
|
|
line = True
|
|
while line:
|
|
line = ins.readline()
|
|
line_count += 1
|
|
if line:
|
|
print(min_indent+line)
|
|
ins.close()
|
|
#if line_count==0:
|
|
#print(min_indent+"print_file WARNING: "+str(line_count)+" line(s) in '"+path+"'")
|
|
#else:
|
|
#print(min_indent+"# "+str(line_count)+" line(s) in '"+path+"'")
|
|
else:
|
|
print (min_indent+"print_file: file does not exist")
|
|
else:
|
|
print (min_indent+"print_file: path is None")
|
|
except:
|
|
print(min_indent+"print_file: could not finish")
|
|
try:
|
|
ins.close()
|
|
except:
|
|
pass
|
|
return line_count
|
|
|
|
def singular_or_plural(singular, plural, count):
|
|
result = plural
|
|
|
|
if count==1:
|
|
result = singular
|
|
return str(count)+" "+result
|
|
|
|
def get_dict_entries_modified_count():
|
|
|
|
return dict_entries_modified_count
|
|
|
|
def get_dict_modified_by_conf_file(this_dict, path, assignment_operator="=", comment_delimiter="#", inline_comments_enable=False):
|
|
global dict_entries_modified_count
|
|
dict_entries_modified_count = 0
|
|
results = this_dict
|
|
#print ("Checking "+str(path)+" for settings...")
|
|
if (results is None) or (type(results) is not dict):
|
|
results = {}
|
|
if os.path.isfile(path):
|
|
ins = open(path, 'r')
|
|
line = True
|
|
while line:
|
|
line = ins.readline()
|
|
if line and len(line)>0:
|
|
line_strip=line.strip()
|
|
if len(line_strip)>0 and line_strip[0]!=comment_delimiter: # if not comment
|
|
if not line_strip[0]=="-": # ignore yaml arrays
|
|
if inline_comments_enable:
|
|
comment_index = line_strip.find(comment_delimiter)
|
|
ao_index = line_strip.find(assignment_operator)
|
|
if ao_index>=1: # intentionally skip zero-length variable names
|
|
if ao_index<len(line_strip)-1: # skip yaml implicit nulls or yaml objects
|
|
result_name = line_strip[:ao_index].strip()
|
|
result_value = line_strip[ao_index+1:].strip()
|
|
result_lower = result_value.lower()
|
|
if result_value=="None" or result_value=="null" or result_value=="~" or result_value=="NULL":
|
|
result_value = None
|
|
elif result_lower=="true":
|
|
result_value = True
|
|
elif result_lower=="false":
|
|
result_value = False
|
|
elif RepresentsInt(result_value):
|
|
result_value = int(result_value)
|
|
elif RepresentsFloat(result_value):
|
|
result_value = float(result_value)
|
|
#print (" CHECKING... "+result_name+":"+result_value)
|
|
if (result_name not in results) or (results[result_name]!=result_value):
|
|
dict_entries_modified_count += 1
|
|
#print(str(dict_entries_modified_count))
|
|
results[result_name]=result_value
|
|
ins.close()
|
|
return results
|
|
|
|
def save_conf_from_dict(path, this_dict, assignment_operator="=", save_nulls_enable=True):
|
|
try:
|
|
outs = open(path, 'w')
|
|
for this_key in this_dict.keys():
|
|
if save_nulls_enable or (this_dict[this_key] is not None):
|
|
if this_dict[this_key] is None:
|
|
outs.write(this_key+assignment_operator+"null\n")
|
|
else:
|
|
outs.write(this_key+assignment_operator+str(this_dict[this_key])+"\n")
|
|
outs.close()
|
|
except:
|
|
print("Could not finish saving chunk metadata to '"+str(path)+"': "+str(traceback.format_exc()))
|
|
try:
|
|
outs.close()
|
|
except:
|
|
pass
|
|
|
|
def get_list_from_hex(hex_string):
|
|
results = None
|
|
if hex_string is not None:
|
|
if len(hex_string)>=2:
|
|
if hex_string[:2]=="0x":
|
|
hex_string = hex_string[2:]
|
|
elif hex_string[:1]=="#":
|
|
hex_string = hex_string[1:]
|
|
if len(hex_string)>0 and hex_string[len(hex_string)-1:]=="h":
|
|
hex_string = hex_string[:len(hex_string)-1]
|
|
index = 0
|
|
while index<len(hex_string):
|
|
if results is None:
|
|
results = list()
|
|
if len(hex_string)-index >= 2:
|
|
results.append(int(hex_string[index:index+2], 16))
|
|
index += 2
|
|
|
|
return results
|
|
|
|
def get_tuple_from_notation(line, debug_src_name="<unknown object>"):
|
|
result = None
|
|
if line is not None:
|
|
# mark chunk
|
|
tuple_noparen_pos_string = line.strip("() \n\r")
|
|
pos_strings = tuple_noparen_pos_string.split(",")
|
|
if len(pos_strings) == 3:
|
|
try:
|
|
player_x = float(pos_strings[0])
|
|
player_y = float(pos_strings[1])
|
|
player_z = float(pos_strings[2])
|
|
except:
|
|
player_x = int(pos_strings[0])
|
|
player_y = int(pos_strings[1])
|
|
player_z = int(pos_strings[2])
|
|
result = player_x, player_y, player_z
|
|
else:
|
|
print("'"+debug_src_name+"' has bad position data--should be 3-length (x,y,z) in position value: "+str(pos_strings))
|
|
return result
|
|
|
|
def is_same_fvec3(list_a, list_b):
|
|
result = False
|
|
if list_a is not None and list_b is not None:
|
|
if len(list_a)>=3 and len(list_b)>=3:
|
|
result = (float(list_a[0]) == float(list_b[0])) and (float(list_a[1]) == float(list_b[1])) and (float(list_a[2]) == float(list_b[2]))
|
|
return False
|
|
|
|
def lastchar(val):
|
|
result = None
|
|
if (val is not None) and (len(val) > 0):
|
|
result = val[len(val)-1]
|
|
return result
|
|
|
|
def get_indent_string(line):
|
|
ender_index = find_any_not(line," \t")
|
|
result = ""
|
|
if ender_index > -1:
|
|
result = line[:ender_index]
|
|
return result
|
|
|
|
def is_identifier_valid(val, is_dot_allowed):
|
|
result = False
|
|
these_id_chars = identifier_chars
|
|
if is_dot_allowed:
|
|
these_id_chars = identifier_and_dot_chars
|
|
for index in range(0,len(val)):
|
|
if val[index] in these_id_chars:
|
|
result = True
|
|
else:
|
|
result = False
|
|
break
|
|
return result
|
|
|
|
|
|
#formerly get_params_len
|
|
def get_operation_chunk_len(val, start=0, step=1, line_counting_number=None):
|
|
result = 0
|
|
openers = "([{"
|
|
closers = ")]}"
|
|
quotes = "'\""
|
|
ender = len(val)
|
|
direction_msg = "after opening"
|
|
if step < 0:
|
|
tmp = openers
|
|
openers = closers
|
|
closers = tmp
|
|
ender = -1
|
|
direction_msg = "before closing"
|
|
opens = ""
|
|
closes = ""
|
|
index = start
|
|
in_quote = None
|
|
line_message = ""
|
|
if (line_counting_number is not None) and (line_counting_number>-1):
|
|
line_message = "line "+str(line_counting_number)+": "
|
|
while (step > 0 and index < ender) or (step < 0 and index > ender):
|
|
opener_number = openers.find(val[index])
|
|
closer_number = closers.find(val[index])
|
|
expected_closer = None
|
|
if (len(closes)>0):
|
|
expected_closer = lastchar(closes)
|
|
quote_number = quotes.find(val[index])
|
|
if (in_quote == None) and (opener_number > -1):
|
|
opens += openers[opener_number]
|
|
closes += closers[opener_number]
|
|
elif (in_quote == None) and (closer_number > -1):
|
|
if closers[closer_number] == expected_closer:
|
|
opens = opens[:len(opens)-1]
|
|
closes = closes[:len(closes)-1]
|
|
elif quote_number > -1:
|
|
if in_quote is None:
|
|
in_quote = val[index]
|
|
else:
|
|
if in_quote == val[index]:
|
|
if (index-1 == -1) or (val[index-1]!="\\"):
|
|
in_quote = None
|
|
index += step
|
|
result += 1
|
|
if (in_quote is None) and (len(opens)==0) and ((index>=len(val)) or (val[index] not in identifier_and_dot_chars)):
|
|
break
|
|
return result
|
|
|
|
def find_identifier(line, identifier_string, start=0):
|
|
result = -1
|
|
start_index = start
|
|
if (identifier_string is not None) and (len(identifier_string) > 0) and (line is not None) and (len(line) > 0):
|
|
while True:
|
|
try_index = find_unquoted_not_commented(line, identifier_string, start=start_index)
|
|
if (try_index > -1):
|
|
if ((try_index==0) or (line[try_index-1] not in identifier_chars)) and ((try_index+len(identifier_string)==len(line)) or (line[try_index+len(identifier_string)] not in identifier_chars)):
|
|
result = try_index
|
|
#input(identifier_string+"starts after '"+line[try_index]+"' ends before '"+line[try_index+len(identifier_string)]+"'")
|
|
break
|
|
else:
|
|
#match is part of a different identifier, so skip it
|
|
#input(identifier_string+" does not after '"+line[try_index]+"' ends before '"+line[try_index+len(identifier_string)]+"'")
|
|
start_index = try_index + len(identifier_string)
|
|
else:
|
|
break
|
|
return result
|
|
|
|
def get_newline_in_data(data):
|
|
newline = None
|
|
cr = "\r"
|
|
lf = "\n"
|
|
cr_index = -1
|
|
lf_index = -1
|
|
cr_index = data.find(cr)
|
|
lf_index = data.find(lf)
|
|
if (cr_index > -1) and (lf_index > -1):
|
|
if cr_index < lf_index:
|
|
newline = cr+lf
|
|
else:
|
|
newline = lf+cr
|
|
elif cr_index > -1:
|
|
newline = cr
|
|
elif lf_index > -1:
|
|
newline = lf
|
|
return newline
|
|
|
|
def re_escape_visible(val):
|
|
result = val.replace("\n","\\n").replace("\n","\\n")
|
|
return result
|
|
|
|
def get_newline(file_path):
|
|
data = None
|
|
with open (file_path, "r") as myfile:
|
|
data=myfile.read()
|
|
return get_newline_in_data(data)
|
|
|
|
|
|
|
|
def is_allowed_in_variable_name_char(one_char):
|
|
result = False
|
|
if len(one_char) == 1:
|
|
if one_char in identifier_chars:
|
|
result = True
|
|
else:
|
|
print("error in is_allowed_in_variable_name_char: one_char must be 1 character")
|
|
return result
|
|
|
|
def find_any_not(haystack, char_needles, start=None, step = 1):
|
|
result = -1
|
|
if (len(char_needles)>0) and (len(haystack)>0):
|
|
endbefore = len(haystack)
|
|
if start is None:
|
|
if step > 0:
|
|
start = 0
|
|
elif step < 0:
|
|
start = len(haystack)-1
|
|
if step < 0:
|
|
endbefore = -1
|
|
index = start
|
|
|
|
while (step>0 and index<endbefore) or (step<0 and index>endbefore):
|
|
if not haystack[index:index+1] in char_needles:
|
|
result = index
|
|
break
|
|
index += step
|
|
return result
|
|
|
|
def explode_unquoted(haystack, delimiter):
|
|
elements = list()
|
|
while True:
|
|
index = find_unquoted_not_commented(haystack, delimiter)
|
|
if index >= 0:
|
|
elements.append(haystack[:index])
|
|
haystack = haystack[index+1:]
|
|
else:
|
|
break
|
|
elements.append(haystack) #rest of haystack is the param after last comma, else beginning if none
|
|
return elements
|
|
|
|
#Finds needle in haystack where not quoted, taking into account escape
|
|
# sequence for single-quoted or double-quoted string inside haystack.
|
|
def find_unquoted_MAY_BE_COMMENTED(haystack, needle, start=0, endbefore=-1, step=1):
|
|
result = -1
|
|
|
|
prev_char = None
|
|
if (haystack is not None) and (needle is not None) and (len(needle)>0):
|
|
in_quote = None
|
|
if endbefore > len(haystack):
|
|
endbefore = len(haystack)
|
|
if endbefore<0:
|
|
endbefore = len(haystack)
|
|
index = start
|
|
if step<0:
|
|
index = endbefore - 1
|
|
if verbose_enable:
|
|
print(" find_unquoted_not_commented in "+haystack.strip()+":")
|
|
while (step>0 and index<=(endbefore-len(needle))) or (step<0 and (index>=0)):
|
|
this_char = haystack[index:index+1]
|
|
if verbose_enable:
|
|
print(" {"
|
|
+"index:"+str(index)+";"
|
|
+"this_char:"+str(this_char)+";"
|
|
+"in_quote:"+str(in_quote)+";"
|
|
+"}")
|
|
if in_quote is None:
|
|
if (this_char == '"') or (this_char == "'"):
|
|
in_quote = this_char
|
|
elif haystack[index:index+len(needle)] == needle:
|
|
result = index
|
|
break
|
|
else:
|
|
if (this_char == in_quote) and (prev_char != "\\"):
|
|
in_quote = None
|
|
elif haystack[index:index+len(needle)] == needle:
|
|
result = index
|
|
break
|
|
prev_char = this_char
|
|
index += step
|
|
return result
|
|
|
|
#DISCARDS whitespace, and never matches None to None
|
|
def find_dup(this_list, discard_whitespace_ignore_None_enable=True, ignore_list=None, ignore_numbers_enable=False):
|
|
result = -1
|
|
|
|
if type(this_list) is list:
|
|
for i1 in range(0,len(this_list)):
|
|
for i2 in range(0,len(this_list)):
|
|
i1_strip = None
|
|
i2_strip = None
|
|
if this_list[i1] is not None:
|
|
i1_strip = this_list[i1].strip()
|
|
if this_list[i2] is not None:
|
|
i2_strip = this_list[i2].strip()
|
|
if i1_strip!=None and len(i1_strip)>0 and i2_strip!=None and len(i2_strip)>0:
|
|
if (i1!=i2) and (ignore_list is None or i1_strip not in ignore_list) and i1_strip==i2_strip:
|
|
number1 = None
|
|
#number2 = None
|
|
if ignore_numbers_enable:
|
|
try:
|
|
number1 = int(i1_strip)
|
|
except:
|
|
try:
|
|
number1 = float(i1_strip)
|
|
except:
|
|
pass
|
|
#only need one since they already are known to match as text
|
|
#try:
|
|
#number2 = int(i2_strip)
|
|
#except:
|
|
#try:
|
|
#number2 = float(i2_strip)
|
|
#except:
|
|
#pass
|
|
if (ignore_numbers_enable and number1 is None) or ((not ignore_numbers_enable)):
|
|
result = i2
|
|
if verbose_enable:
|
|
print("["+str(i1)+"]:"+str(this_list[i1])+" matches ["+str(i2)+"]:"+str(this_list[i2]))
|
|
break
|
|
if result>-1:
|
|
break
|
|
else:
|
|
input("ERROR in has_dups: "+str(this_list)+" is not a list")
|
|
return result
|
|
def has_dups(this_list):
|
|
return find_dup(this_list)>-1
|
|
#region formerly pcttext.py
|
|
|
|
#gets first instance of name, gets its value, then stops reading file
|
|
def get_initial_value_from_conf(path, name, assignment_operator="="):
|
|
result = None
|
|
line_count = 0
|
|
if path is not None:
|
|
if os.path.isfile(path):
|
|
ins = open(path, 'r')
|
|
line = True
|
|
while line:
|
|
line = ins.readline()
|
|
line_count += 1
|
|
if line:
|
|
ao_i=line.find(assignment_operator)
|
|
if ao_i>0: # intentionall skip when 0
|
|
this_name = line[:ao_i].strip()
|
|
if this_name==name:
|
|
result = line[ao_i+1:].strip() #NOTE: blank is allowed
|
|
break
|
|
ins.close()
|
|
else:
|
|
print("ERROR in get_initial_value_from_conf: '"+str(path)+"' is not a file.")
|
|
else:
|
|
print("ERROR in get_initial_value_from_conf: path is None.")
|
|
return result
|
|
|
|
def find_unquoted_not_commented(haystack, needle, start=0, endbefore=-1, step=1, comment_delimiter="#"):
|
|
result = -1
|
|
|
|
prev_char = None
|
|
if (haystack is not None) and (needle is not None) and (len(needle)>0):
|
|
in_quote = None
|
|
if endbefore > len(haystack):
|
|
endbefore = len(haystack)
|
|
if endbefore<0:
|
|
endbefore = len(haystack)
|
|
index = start
|
|
if step<0:
|
|
index = endbefore - 1
|
|
if verbose_enable:
|
|
print(" find_unquoted_not_commented in "+haystack.strip()+":")
|
|
while (step>0 and index<=(endbefore-len(needle))) or (step<0 and (index>=0)):
|
|
this_char = haystack[index:index+1]
|
|
if verbose_enable:
|
|
print(" {"
|
|
+"index:"+str(index)+";"
|
|
+"this_char:"+str(this_char)+";"
|
|
+"in_quote:"+str(in_quote)+";"
|
|
+"}")
|
|
if in_quote is None:
|
|
if (this_char == comment_delimiter) or (haystack[index:index+3]=="\"\"\""):
|
|
break
|
|
elif (this_char == '"') or (this_char == "'"):
|
|
in_quote = this_char
|
|
elif haystack[index:index+len(needle)] == needle:
|
|
result = index
|
|
break
|
|
else:
|
|
if (this_char == in_quote) and (prev_char != "\\"):
|
|
in_quote = None
|
|
elif haystack[index:index+len(needle)] == needle:
|
|
result = index
|
|
break
|
|
prev_char = this_char
|
|
index += step
|
|
return result
|
|
|
|
#endregion formerly pcttext.py
|
|
|