Browse Source
Adding a utility script to compare the stylers model to themes. This script can be used to detect missing lexer types as well as word styles for said lexer types. Signed-off-by: James Knight <git@jdknight.me>pull/15816/head
James Knight
4 days ago
1 changed files with 205 additions and 0 deletions
@ -0,0 +1,205 @@
|
||||
#!/usr/bin/env python |
||||
# SPDX-License-Identifier: GPL-3.0-or-later |
||||
# |
||||
# Introduces a styles check utility. This can be used to help check for |
||||
# possible updates needed to apply for a given theme. |
||||
|
||||
from enum import Enum |
||||
from enum import auto |
||||
from pathlib import Path |
||||
import xml.etree.ElementTree as ET |
||||
import argparse |
||||
|
||||
# version of this script |
||||
__version__ = '0.0.0' |
||||
|
||||
# default filename for the stylers model |
||||
DEFAULT_STYLERS_MODEL = 'stylers.model.xml' |
||||
|
||||
# expected tag at the root of the xml model/style document |
||||
XML_ROOT_TAG = 'NotepadPlus' |
||||
|
||||
# xml query for finding lexer types |
||||
XML_MATCH_LEXER_TYPE = './LexerStyles/LexerType' |
||||
|
||||
# xml query for finding word styles |
||||
XML_MATCH_WORD_STYLES = './WordsStyle' |
||||
|
||||
|
||||
def main(): |
||||
parser = argparse.ArgumentParser() |
||||
parser.add_argument('themes', nargs='*', |
||||
help='Theme(s) to check against') |
||||
parser.add_argument('--all', '-A', action='store_true', |
||||
help='Perform all checks') |
||||
parser.add_argument('--model', |
||||
help=f'The stylers model definition (default: {DEFAULT_STYLERS_MODEL})') |
||||
parser.add_argument('--version', '-v', action='version', |
||||
version='%(prog)s ' + __version__) |
||||
parser.add_argument('--ws', action='store_true', |
||||
help='Include checks for word styles') |
||||
args = parser.parse_args() |
||||
|
||||
# find default paths |
||||
pe_dir = Path(__file__).parent.parent |
||||
src_dir = pe_dir / 'src' |
||||
installer_dir = pe_dir / 'installer' |
||||
default_themes_dir = installer_dir / 'themes' |
||||
|
||||
# determine stylers model to use |
||||
if args.model: |
||||
stylers_model = Path(args.model) |
||||
else: |
||||
stylers_model = src_dir / DEFAULT_STYLERS_MODEL |
||||
|
||||
if not stylers_model.is_file(): |
||||
raise SystemExit(f'missing theme model: {stylers_model}') |
||||
|
||||
# compile a list of themes to check against |
||||
theme_search_list = args.themes |
||||
if not args.themes: |
||||
theme_search_list = [ default_themes_dir ] |
||||
|
||||
theme_files = [] |
||||
unknown_themes = [] |
||||
for theme_arg in theme_search_list: |
||||
theme_path = Path(theme_arg) |
||||
if theme_path.is_file(): |
||||
theme_files.append(theme_path) |
||||
elif theme_path.is_dir(): |
||||
found_themes = theme_path.glob('*.xml') |
||||
for found_theme in found_themes: |
||||
if found_theme.is_file(): |
||||
theme_files.append(found_theme) |
||||
else: |
||||
unknown_themes.append(theme_path) |
||||
|
||||
for unknown_theme in unknown_themes: |
||||
possible_theme = default_themes_dir / f'{unknown_theme}.xml' |
||||
if possible_theme.is_file(): |
||||
theme_files.append(possible_theme) |
||||
else: |
||||
raise SystemExit(f'unknown theme provided: {theme_path}') |
||||
|
||||
if not theme_files: |
||||
raise SystemExit('no themes to process') |
||||
|
||||
# build options and perform a check |
||||
opts = { |
||||
'model': stylers_model, |
||||
'themes': theme_files, |
||||
'word-styles': args.all or args.ws, |
||||
} |
||||
|
||||
issue_count = check(opts) |
||||
|
||||
if issue_count == 0: |
||||
print('\nLooks good!') |
||||
else: |
||||
raise SystemExit(f'\nDetected an issue (total: {issue_count})!') |
||||
|
||||
|
||||
def check(opts): |
||||
issue_count = 0 |
||||
|
||||
print(f'Loading styles model: {opts['model']}') |
||||
tree = ET.parse(opts['model']) |
||||
root = tree.getroot() |
||||
if root.tag != XML_ROOT_TAG: |
||||
raise SystemExit(f'unknown model definition (no "{XML_ROOT_TAG}" tag)') |
||||
|
||||
default_db = extract_style_lexers(root) |
||||
|
||||
print(f'Styles model defines {len(default_db)} lexers types.') |
||||
theme_count = len(opts['themes']) |
||||
if theme_count > 1: |
||||
print(f'Processing {theme_count} themes.') |
||||
print() |
||||
|
||||
# sort lexer styles to give a nicer presentation |
||||
lexer_types = {} |
||||
for k, v in default_db.items(): |
||||
lexer_types[k] = v[DbKey.Description] |
||||
lexer_types = dict(sorted(lexer_types.items(), key=lambda item: item[1])) |
||||
|
||||
# process each provided theme |
||||
for xmlfile in opts['themes']: |
||||
tree = ET.parse(xmlfile) |
||||
root = tree.getroot() |
||||
if root.tag != 'NotepadPlus': |
||||
continue |
||||
|
||||
print(xmlfile.stem) |
||||
db = extract_style_lexers(root) |
||||
|
||||
missing_lexers = default_db.keys() - db.keys() |
||||
unknown_lexers = db.keys() - default_db.keys() |
||||
matching_lexers = set(default_db.keys()).intersection(db.keys()) |
||||
|
||||
for lexer_type, lexer_name in lexer_types.items(): |
||||
if lexer_type in missing_lexers: |
||||
print(f' MISSING: {lexer_name} ({lexer_type})') |
||||
issue_count += 1 |
||||
continue |
||||
|
||||
for unknown_lexer in unknown_lexers: |
||||
ule_desc = db[unknown_lexer].get(DbKey.Description, unknown_lexer) |
||||
print(f' UNKNOWN: {ule_desc} ({unknown_lexer})') |
||||
issue_count += 1 |
||||
|
||||
# check for any word style inconsistencies |
||||
if opts['word-styles']: |
||||
for lexer_type in matching_lexers: |
||||
default_ws = default_db[lexer_type][DbKey.WordsStyle] |
||||
db_ws = db[lexer_type][DbKey.WordsStyle] |
||||
|
||||
missing_ws = default_ws.keys() - db_ws.keys() |
||||
unknown_ws = db_ws.keys() - default_ws.keys() |
||||
|
||||
if missing_ws or unknown_ws: |
||||
lexer_name = lexer_types[lexer_type] |
||||
print(f' PARTIAL: {lexer_name} ({lexer_type})') |
||||
|
||||
for missing in missing_ws: |
||||
ws_name = default_ws[missing] |
||||
print(f' MISSING: {ws_name} ({missing})') |
||||
issue_count += 1 |
||||
|
||||
for unknown in unknown_ws: |
||||
ws_name = db_ws[unknown] |
||||
print(f' UNKNOWN: {ws_name} ({unknown})') |
||||
issue_count += 1 |
||||
|
||||
return issue_count |
||||
|
||||
|
||||
class DbKey(Enum): |
||||
Description = auto() |
||||
WordsStyle = auto() |
||||
|
||||
|
||||
def extract_style_lexers(root): |
||||
lexer_db = {} |
||||
|
||||
for lexer_entry in root.findall(XML_MATCH_LEXER_TYPE): |
||||
lexer_name = lexer_entry.attrib.get('name') |
||||
lexer_desc = lexer_entry.attrib.get('desc') |
||||
|
||||
lexer_db[lexer_name] = { |
||||
DbKey.Description: lexer_desc, |
||||
DbKey.WordsStyle: {}, |
||||
} |
||||
|
||||
for lexer_ws in lexer_entry.findall(XML_MATCH_WORD_STYLES): |
||||
ws_id = lexer_ws.attrib.get('styleID') |
||||
if not ws_id: |
||||
continue |
||||
|
||||
ws_name = lexer_ws.attrib.get('name') |
||||
lexer_db[lexer_name][DbKey.WordsStyle][ws_id] = ws_name |
||||
|
||||
return lexer_db |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
main() |
Loading…
Reference in new issue