update requirement package + add replace_attrs scripts
This commit is contained in:
parent
0782ace6b4
commit
8bd3923a93
2
exercise
2
exercise
@ -1 +1 @@
|
||||
Subproject commit 4b8cd12232cd226a7d3f952cd2644f31cea402a6
|
||||
Subproject commit c06c5500ea2c47d9e6a8e1e84d183f17e4b9f29b
|
@ -104,4 +104,8 @@ requests ; python_version > '3.10'
|
||||
google_auth ; python_version > '3.10'
|
||||
odoorpc ; python_version > '3.10'
|
||||
openai ; python_version > '3.10'
|
||||
pysftp ; python_version > '3.10'
|
||||
pysftp ; python_version > '3.10' and sys_platform != 'win32'
|
||||
unidecode ; python_version > '3.10'
|
||||
pdfkit ; python_version > '3.10'
|
||||
weasyprint
|
||||
beautifulsoup4
|
244
setup/replace_attrs.py
Normal file
244
setup/replace_attrs.py
Normal file
@ -0,0 +1,244 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import re
|
||||
from bs4 import formatter, BeautifulSoup as bs
|
||||
from pathlib import Path
|
||||
|
||||
xml_4indent_formatter = formatter.XMLFormatter(indent=4)
|
||||
NEW_ATTRS = {'required', 'invisible', 'readonly', 'column_invisible'}
|
||||
percent_d_regex = re.compile("%\('?\"?[\w\.\d_]+'?\"?\)d")
|
||||
|
||||
def get_files_recursive(path):
|
||||
return (str(p) for p in Path(path).glob('**/*.xml') if p.is_file())
|
||||
|
||||
root_dir = input('Enter root directory to check (empty for current directory) : ')
|
||||
root_dir = root_dir or '.'
|
||||
all_xml_files = get_files_recursive(root_dir)
|
||||
|
||||
def normalize_domain(domain):
|
||||
"""Normalize Domain, taken from odoo/osv/expression.py -> just the part so that & operators are added where needed.
|
||||
After that, we can use a part of the def parse() from the same file to manage parenthesis for and/or"""
|
||||
if len(domain) == 1:
|
||||
return domain
|
||||
result = []
|
||||
expected = 1 # expected number of expressions
|
||||
op_arity = {'!': 1, '&': 2, '|': 2}
|
||||
for token in domain:
|
||||
if expected == 0: # more than expected, like in [A, B]
|
||||
result[0:0] = ['&'] # put an extra '&' in front
|
||||
expected = 1
|
||||
if isinstance(token, (list, tuple)): # domain term
|
||||
expected -= 1
|
||||
token = tuple(token)
|
||||
else:
|
||||
expected += op_arity.get(token, 0) - 1
|
||||
result.append(token)
|
||||
return result
|
||||
|
||||
def stringify_leaf(leaf):
|
||||
stringify = ''
|
||||
switcher = False
|
||||
# Replace operators not supported in python (=, like, ilike)
|
||||
operator = str(leaf[1])
|
||||
if operator == '=':
|
||||
operator = '=='
|
||||
elif 'like' in operator:
|
||||
if 'not' in operator:
|
||||
operator = 'not in'
|
||||
else:
|
||||
operator = 'in'
|
||||
switcher = True
|
||||
# Take left operand, never to add quotes (should be python object / field)
|
||||
left_operand = leaf[0]
|
||||
# Take care of right operand, don't add quotes if it's list/tuple/set/boolean/number, check if we have a true/false/1/0 string tho.
|
||||
right_operand = leaf[2]
|
||||
if right_operand in ('True', 'False', '1', '0') or type(right_operand) in (list, tuple, set, int, float, bool):
|
||||
right_operand = str(right_operand)
|
||||
else:
|
||||
right_operand = "'"+right_operand+"'"
|
||||
stringify = "%s %s %s" % (right_operand if switcher else left_operand, operator, left_operand if switcher else right_operand)
|
||||
return stringify
|
||||
|
||||
def stringify_attr(stack):
|
||||
if stack in (True, False, 'True', 'False', 1, 0, '1', '0'):
|
||||
return stack
|
||||
last_parenthesis_index = max(index for index, item in enumerate(stack[::-1]) if item not in ('|', '!'))
|
||||
stack = normalize_domain(stack)
|
||||
stack = stack[::-1]
|
||||
result = []
|
||||
for index, leaf_or_operator in enumerate(stack):
|
||||
if leaf_or_operator == '!':
|
||||
expr = result.pop()
|
||||
result.append('(not (%s))' % expr)
|
||||
elif leaf_or_operator == '&' or leaf_or_operator == '|':
|
||||
left = result.pop()
|
||||
# In case of a single | or single & , we expect that it's a tag that have an attribute AND a state
|
||||
# the state will be added as OR in states management
|
||||
try:
|
||||
right = result.pop()
|
||||
except IndexError:
|
||||
res = left + ('%s' % ' and' if leaf_or_operator=='&' else ' or')
|
||||
result.append(res)
|
||||
continue
|
||||
form = '(%s %s %s)'
|
||||
if index > last_parenthesis_index:
|
||||
form = '%s %s %s'
|
||||
result.append(form % (left, 'and' if leaf_or_operator=='&' else 'or', right))
|
||||
else:
|
||||
result.append(stringify_leaf(leaf_or_operator))
|
||||
result = result[0]
|
||||
return result
|
||||
|
||||
def get_new_attrs(attrs):
|
||||
new_attrs = {}
|
||||
attrs_dict = eval(attrs.strip())
|
||||
for attr in NEW_ATTRS:
|
||||
if attr in attrs_dict.keys():
|
||||
new_attrs[attr] = stringify_attr(attrs_dict[attr])
|
||||
ordered_attrs = {attr: new_attrs[attr] for attr in NEW_ATTRS if attr in new_attrs}
|
||||
return ordered_attrs
|
||||
|
||||
# Prettify puts <attribute> on three lines (1/ opening tag, 2/ text, 3/ closing tag), not very cool.
|
||||
# Taken from https://stackoverflow.com/questions/55962146/remove-line-breaks-and-spaces-around-span-elements-with-python-regex
|
||||
# And changed to avoid putting ALL one line, and only manage <attribute>, as it's the only one messing stuff here
|
||||
# Kinda ugly to use the 3 types of tags but tbh I keep it like this while I have no time for a regex replace keeping the name="x" :p
|
||||
def prettify_output(html):
|
||||
for attr in NEW_ATTRS:
|
||||
html = re.sub(f'<attribute name="{attr}">[ \n]+',f'<attribute name="{attr}">', html)
|
||||
html = re.sub(f'[ \n]+</attribute>',f'</attribute>', html)
|
||||
html = re.sub(r'<field name="([a-z_]+)">[ \n]+', r'<field name="\1">', html)
|
||||
html = re.sub(r'[ \n]+</field>', r'</field>', html)
|
||||
return html
|
||||
|
||||
autoreplace = input('Do you want to auto-replace attributes ? (y/n) (empty == no) (will not ask confirmation for each file) : ') or 'n'
|
||||
nofilesfound = True
|
||||
ok_files = []
|
||||
nok_files = []
|
||||
|
||||
for xml_file in all_xml_files:
|
||||
try:
|
||||
with open(xml_file, 'rb') as f:
|
||||
contents = f.read().decode('utf-8')
|
||||
f.close()
|
||||
if not 'attrs' in contents and not 'states' in contents:
|
||||
continue
|
||||
counter_for_percent_d_replace = 1
|
||||
percent_d_results = {}
|
||||
for percent_d in percent_d_regex.findall(contents):
|
||||
contents = contents.replace(percent_d, "'REPLACEME%s'" % counter_for_percent_d_replace)
|
||||
percent_d_results[counter_for_percent_d_replace] = percent_d
|
||||
counter_for_percent_d_replace += 1
|
||||
soup = bs(contents, 'xml')
|
||||
tags_with_attrs = soup.select('[attrs]')
|
||||
attribute_tags_name_attrs = soup.select('attribute[name="attrs"]')
|
||||
tags_with_states = soup.select('[states]')
|
||||
attribute_tags_name_states = soup.select('attribute[name="states"]')
|
||||
if not (tags_with_attrs or attribute_tags_name_attrs or\
|
||||
tags_with_states or attribute_tags_name_states):
|
||||
continue
|
||||
print('\n################################################################')
|
||||
print('##### Taking care of file -> %s' % xml_file)
|
||||
print('\n########### Current tags found ###\n')
|
||||
for t in tags_with_attrs + attribute_tags_name_attrs + tags_with_states + attribute_tags_name_states:
|
||||
print(t)
|
||||
|
||||
nofilesfound = False
|
||||
# Management of tags that have attrs=""
|
||||
for tag in tags_with_attrs:
|
||||
attrs = tag['attrs']
|
||||
new_attrs = get_new_attrs(attrs)
|
||||
del tag['attrs']
|
||||
for new_attr in new_attrs.keys():
|
||||
tag[new_attr] = new_attrs[new_attr]
|
||||
# Management of attributes name="attrs"
|
||||
attribute_tags_after = []
|
||||
for attribute_tag in attribute_tags_name_attrs:
|
||||
new_attrs = get_new_attrs(attribute_tag.text)
|
||||
for new_attr in new_attrs.keys():
|
||||
new_tag = soup.new_tag('attribute')
|
||||
new_tag['name'] = new_attr
|
||||
new_tag.append(str(new_attrs[new_attr]))
|
||||
attribute_tags_after.append(new_tag)
|
||||
attribute_tag.insert_after(new_tag)
|
||||
attribute_tag.decompose()
|
||||
# Management ot tags that have states=""
|
||||
for state_tag in tags_with_states:
|
||||
base_invisible = ''
|
||||
if 'invisible' in state_tag.attrs and state_tag['invisible']:
|
||||
base_invisible = state_tag['invisible']
|
||||
if not (base_invisible.endswith('or') or base_invisible.endswith('and')):
|
||||
base_invisible = base_invisible + ' or '
|
||||
else:
|
||||
base_invisible = base_invisible + ' '
|
||||
invisible_attr = "state not in [%s]" % ','.join(("'" + state.strip() + "'") for state in state_tag['states'].split(','))
|
||||
state_tag['invisible'] = base_invisible + invisible_attr
|
||||
del state_tag['states']
|
||||
# Management of attributes name="states"
|
||||
attribute_tags_states_after = []
|
||||
for attribute_tag_states in attribute_tags_name_states:
|
||||
states = attribute_tag_states.text
|
||||
existing_invisible_tag = False
|
||||
# I don't know why, looking for attribute[name="invisible"] does not work,
|
||||
# but if it exists, I can find it with findAll attribute -> loop to name="invisible"
|
||||
for tag in attribute_tag_states.parent.findAll('attribute'):
|
||||
if tag['name'] == 'invisible':
|
||||
existing_invisible_tag = tag
|
||||
break
|
||||
if not existing_invisible_tag:
|
||||
existing_invisible_tag = soup.new_tag('attribute')
|
||||
existing_invisible_tag['name'] = 'invisible'
|
||||
if existing_invisible_tag.text:
|
||||
states_to_add = 'state not in [%s]' % (
|
||||
','.join(("'" + state.strip() + "'") for state in states.split(','))
|
||||
)
|
||||
if existing_invisible_tag.text.endswith('or') or existing_invisible_tag.text.endswith('and'):
|
||||
new_invisible_text = '%s %s' % (existing_invisible_tag.text, states_to_add)
|
||||
else:
|
||||
new_invisible_text = ' or '.join([existing_invisible_tag.text, states_to_add])
|
||||
else:
|
||||
new_invisible_text = 'state not in [%s]' % (
|
||||
','.join(("'" + state.strip() + "'") for state in states.split(','))
|
||||
)
|
||||
existing_invisible_tag.string = new_invisible_text
|
||||
attribute_tag_states.insert_after(existing_invisible_tag)
|
||||
attribute_tag_states.decompose()
|
||||
attribute_tags_states_after.append(existing_invisible_tag)
|
||||
|
||||
print('\n########### Will be replaced by ###\n')
|
||||
for t in tags_with_attrs + attribute_tags_after + tags_with_states + attribute_tags_states_after:
|
||||
print(t)
|
||||
print('################################################################\n')
|
||||
if autoreplace.lower()[0] == 'n':
|
||||
confirm = input('Do you want to replace? (y/n) (empty == no) : ') or 'n'
|
||||
else:
|
||||
confirm = 'y'
|
||||
if confirm.lower()[0] == 'y':
|
||||
with open(xml_file, 'wb') as rf:
|
||||
html = soup.prettify(formatter=xml_4indent_formatter)
|
||||
html = prettify_output(html)
|
||||
for percent_d_result in percent_d_results.keys():
|
||||
html = html.replace("'REPLACEME%s'" % percent_d_result, percent_d_results[percent_d_result])
|
||||
rf.write(html.encode('utf-8'))
|
||||
ok_files.append(xml_file)
|
||||
except Exception as e:
|
||||
nok_files.append((xml_file, e))
|
||||
|
||||
print('\n################################################')
|
||||
print('################## Run Debug ##################')
|
||||
print('################################################')
|
||||
|
||||
if nofilesfound:
|
||||
print('No XML Files with "attrs" or "states" found in dir " %s "' % root_dir)
|
||||
|
||||
print('Succeeded on files')
|
||||
for file in ok_files:
|
||||
print(file)
|
||||
if not ok_files:
|
||||
print('No files')
|
||||
print('')
|
||||
print('Failed on files')
|
||||
for file in nok_files:
|
||||
print(file[0])
|
||||
print('Reason: ', file[1])
|
||||
if not nok_files:
|
||||
print('No files')
|
Loading…
Reference in New Issue
Block a user