Initial commit

This commit is contained in:
AnyOldName3
2019-03-23 22:19:26 +00:00
commit 13ce730ee8
8 changed files with 1231 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
root = true
[*.py]
indent_style = space
indent_size = 4
+109
View File
@@ -0,0 +1,109 @@
# Mod Organizer Umbrella stuff
/msbuild.log
/*std*.log
/*build
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
+15
View File
@@ -0,0 +1,15 @@
# Version chosen arbitrarily
CMAKE_MINIMUM_REQUIRED(VERSION 3.0)
PROJECT(ScriptExtenderPluginChecker LANGUAGES NONE)
# Value passed from modorganizer-umbrella
SET(DEPENDENCIES_DIR CACHE PATH "")
LIST(APPEND CMAKE_PREFIX_PATH ${QT_ROOT}/lib/cmake)
# Find Python
FILE(GLOB_RECURSE PYTHON_ROOT ${DEPENDENCIES_DIR}/pyconfig.h.in)
GET_FILENAME_COMPONENT(PYTHON_ROOT ${PYTHON_ROOT} DIRECTORY)
ADD_SUBDIRECTORY(src)
+674
View File
File diff suppressed because it is too large Load Diff
+16
View File
@@ -0,0 +1,16 @@
# Version chosen arbitrarily
CMAKE_MINIMUM_REQUIRED(VERSION 3.0)
# maybe widgets too
FIND_PACKAGE(Qt5LinguistTools)
INCLUDE(PyQt5TranslationMacros.cmake)
PYQT5_CREATE_TRANSLATION(scriptextenderpluginchecker_translations_qm ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/src/ScriptExtenderPluginChecker_en.ts)
add_custom_target(translations ALL DEPENDS ${scriptextenderpluginchecker_translations_qm})
###############
## Installation
INSTALL(FILES
${CMAKE_CURRENT_SOURCE_DIR}/ScriptExtenderPluginChecker.py
DESTINATION bin/plugins)
+88
View File
@@ -0,0 +1,88 @@
# This is a modified version of Qt5LinguistToolsMacros.cmake which calls
# pylupdate5 instead of lupdate, allowing Python strings to be extracted,
# too. It still requires the Qt version of the file to be included, but
# only this version of the function needs to be called. You also need to
# have PYTHON_ROOT set to a directory where a working pylupdate5.bat can
# be found. If you aren't using Windows, your platform's equivalent may
# work, too.
#=============================================================================
# Copyright 2005-2011 Kitware, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# * Neither the name of Kitware, Inc. nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#=============================================================================
include(CMakeParseArguments)
function(PYQT5_CREATE_TRANSLATION _qm_files)
set(options)
set(oneValueArgs)
set(multiValueArgs OPTIONS)
cmake_parse_arguments(_LUPDATE "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
set(_lupdate_files ${_LUPDATE_UNPARSED_ARGUMENTS})
set(_lupdate_options ${_LUPDATE_OPTIONS})
set(_my_sources)
set(_my_tsfiles)
foreach(_file ${_lupdate_files})
get_filename_component(_ext ${_file} EXT)
get_filename_component(_abs_FILE ${_file} ABSOLUTE)
if(_ext MATCHES "ts")
list(APPEND _my_tsfiles ${_abs_FILE})
else()
list(APPEND _my_sources ${_abs_FILE})
endif()
endforeach()
foreach(_ts_file ${_my_tsfiles})
set(_lst_file_srcs)
if(_my_sources)
# Qt made a file listing all sources and used that as an argument, but pylupdate5 doesn't support that.
# Qt allowed directories to be listed as sources, but pylupdate5 requires their contents to be listed.
get_filename_component(_ts_name ${_ts_file} NAME_WE)
set(_ts_lst_file "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${_ts_name}_lst_file")
foreach(_lst_file_src ${_my_sources})
if(IS_DIRECTORY ${_lst_file_src})
file(GLOB _directory_contents ${_lst_file_src}/*.py ${_lst_file_src}/*.ui)
list(APPEND _lst_file_srcs ${_directory_contents})
else()
list(APPEND _lst_file_srcs ${_lst_file_src})
endif()
endforeach()
endif()
add_custom_command(OUTPUT ${_ts_file}
COMMAND ${PYTHON_ROOT}/pylupdate5.bat
ARGS ${_lupdate_options} ${_lst_file_srcs} -ts ${_ts_file}
DEPENDS ${_lst_file_srcs} ${_ts_lst_file}
WORKING_DIRECTORY ${PYTHON_ROOT}
VERBATIM)
endforeach()
qt5_add_translation(${_qm_files} ${_my_tsfiles})
set(${_qm_files} ${${_qm_files}} PARENT_SCOPE)
endfunction()
+233
View File
@@ -0,0 +1,233 @@
from enum import Enum, auto
from pathlib import Path
import re
import sys
from PyQt5.QtCore import QCoreApplication, qDebug
if "mobase" not in sys.modules:
import mock_mobase as mobase
class PluginMessage():
def __init__(self, pluginPath):
self._pluginPath = Path(pluginPath)
def successful(self):
return False
def asMessage(self):
return self.__tr("{0} existed.").format(self._pluginPath.name)
def __tr(self, str):
return QCoreApplication.translate("PluginMessage", str)
messageTypes = []
@staticmethod
def registerMessageType(messageType):
PluginMessage.messageTypes.append(messageType)
@staticmethod
def PluginMessageFactory(line):
for messageType in PluginMessage.messageTypes:
match = messageType[0].fullmatch(line)
if match:
return messageType[1](match)
return None
class NormalPluginMessage(PluginMessage):
def __init__(self, match):
super(NormalPluginMessage, self).__init__(match.group("pluginPath"))
self.__infoVersion = int(match.group("infoVersion"), 16)
self.__name = match.group("name")
self.__version = int(match.group("version"), 16)
self.__loadStatus = match.group("loadStatus")
def successful(self):
return self.__loadStatus == "loaded correctly"
def asMessage(self):
return self.__tr("{0} version {1} ({2}) {3}.").format(self.__name, self.__version, self._pluginPath.name, self.__trLoadStatus())
def __trLoadStatus(self):
# We need to list the possible options so they get detected as translatable strings.
loadStatusTranslations = {
"loaded correctly" : self.__tr("loaded correctly"),
"reported as incompatible during query" : self.__tr("reported as incompatible during query"),
"reported as incompatible during load" : self.__tr("reported as incompatible during load"),
"disabled, fatal error occurred while loading plugin" : self.__tr("disabled, fatal error occurred while loading plugin"),
"disabled, no name specified" : self.__tr("disabled, no name specified"),
"disabled, fatal error occurred while checking plugin compatibility" : self.__tr("disabled, fatal error occurred while checking plugin compatibility"),
"disabled, fatal error occurred while querying plugin" : self.__tr("disabled, fatal error occurred while querying plugin")
}
if self.__loadStatus in loadStatusTranslations:
return loadStatusTranslations[self.__loadStatus]
else:
# There's a blacklist of known broken plugins that get excluded, each with their own reason.
# We aren't translating that.
return self.__loadStatus
def __tr(self, str):
return QCoreApplication.translate("NormalPluginMessage", str)
PluginMessage.registerMessageType((re.compile(r"plugin (?P<pluginPath>.+) \((?P<infoVersion>[\dA-Fa-f]{8}) (?P<name>.+) (?P<version>[\dA-Fa-f]{8})\) (?P<loadStatus>.+)\s"), NormalPluginMessage))
class CouldntLoadPluginMessage(PluginMessage):
def __init__(self, match):
super(CouldntLoadPluginMessage, self).__init__(match.group("pluginPath"))
self.__lastError = int(match.group("lastError"))
def successful(self):
return False
def asMessage(self):
return self.__tr("Couldn't load {0}. The last error code was {1}.").format(self._pluginPath.name, self.__lastError)
def __tr(self, str):
return QCoreApplication.translate("CouldntLoadPluginMessage", str)
PluginMessage.registerMessageType((re.compile(r"couldn't load plugin (?P<pluginPath>.+) \(Error (?P<lastError>[-+]?\d+)\)\s"), CouldntLoadPluginMessage))
class NotAPluginMessage(PluginMessage):
def __init__(self, match):
super(NotAPluginMessage, self).__init__(match.group("pluginPath"))
def successful(self):
return False
def asMessage(self):
return self.__tr("{0} does not appear to be an SKSE plugin.").format(self._pluginPath.name)
def __tr(self, str):
return QCoreApplication.translate("NotAPluginMessage", str)
PluginMessage.registerMessageType((re.compile(r"plugin (?P<pluginPath>.+) does not appear to be an (?:SK)|(?:F4)|(?:NV)|(?:FO)|(?:OB)SE plugin\s"), NotAPluginMessage))
class LogLocation(Enum):
DOCS = auto()
INSTALL = auto()
class ScriptExtenderPluginChecker(mobase.IPluginDiagnose):
supportedGames = {
"Skyrim" : (LogLocation.DOCS, Path("SKSE") / "skse.log"),
"Skyrim Special Edition" : (LogLocation.DOCS, Path("SKSE") / "skse64.log"),
"Skyrim VR" : (LogLocation.DOCS, Path("SKSE") / "sksevr.log"),
#"Enderal" : (LogLocation.DOCS, Path("")),
"Fallout 4" : (LogLocation.DOCS, Path("F4SE") / "f4se.log"),
"Oblivion" : (LogLocation.INSTALL, Path("obse.log")),
"New Vegas" : (LogLocation.INSTALL, Path("nvse.log")),
"TTW" : (LogLocation.INSTALL, Path("ttw_nvse.log")),
"Fallout 3" : (LogLocation.INSTALL, Path("fose.log"))
}
def __init__(self):
super(ScriptExtenderPluginChecker, self).__init__()
self.__organizer = None
def init(self, organizer):
self.__organizer = organizer
organizer.onFinishedRun(lambda a, b: self._invalidate())
return True
def name(self):
return "Script Extender Plugin Load Checker"
def author(self):
return "AnyOldName3"
def description(self):
return self.__tr("Checks script extender log to see if any plugins failed to load.")
def version(self):
return mobase.VersionInfo(1, 0, 0, mobase.ReleaseType.prealpha)
def isActive(self):
# This is never called, but it should be.
return self.__organizer.managedGame().gameName() in self.supportedGames
def settings(self):
return []
def activeProblems(self):
if self.__scanLog():
return [0]
else:
return []
def shortDescription(self, key):
return self.__tr("Script extender log reports incompatible plugins.")
def fullDescription(self, key):
pluginList = self.__listBadPluginMessagess()
pluginListString = "\n" + ("\n".join(pluginList))
return self.__tr("You have one or more script extender plugins which failed to load. They are:{0}").format(pluginListString)
def hasGuidedFix(self, key):
return False
def startGuidedFix(self, key):
pass
def __tr(self, str):
return QCoreApplication.translate("ScriptExtenderPluginChecker", str)
def __scanLog(self):
if self.__organizer.managedGame().gameName() not in self.supportedGames:
return False
base, suffix = self.supportedGames[self.__organizer.managedGame().gameName()]
if base == LogLocation.DOCS:
base = Path(self.__organizer.managedGame().documentsDirectory().absolutePath())
elif base == LogLocation.INSTALL:
base = Path(self.__organizer.managedGame().gameDirectory().absolutePath())
logPath = base / suffix
try:
with logPath.open('r') as logFile:
for line in logFile:
pluginMessage = PluginMessage.PluginMessageFactory(line)
if pluginMessage and not pluginMessage.successful():
return True
except Exception:
# There's almost certainly just no log yet
pass
return False
def __listBadPluginMessagess(self):
base, suffix = self.supportedGames[self.__organizer.managedGame().gameName()]
if base == LogLocation.DOCS:
base = Path(self.__organizer.managedGame().documentsDirectory().absolutePath())
elif base == LogLocation.INSTALL:
base = Path(self.__organizer.managedGame().gameDirectory().absolutePath())
logPath = base / suffix
messageList = []
try:
with logPath.open('r') as logFile:
for line in logFile:
pluginMessage = PluginMessage.PluginMessageFactory(line)
if pluginMessage and not pluginMessage.successful():
messageList.append(pluginMessage.asMessage())
except Exception:
# There's almost certainly just no log yet
pass
return messageList
def createPlugin():
return ScriptExtenderPluginChecker()
+91
View File
@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS><TS version="2.0">
<context>
<name>CouldntLoadPluginMessage</name>
<message>
<location filename="SKSEPluginChecker.py" line="87"/>
<source>Couldn&apos;t load {0}. The last error code was {1}.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>LogLocation</name>
</context>
<context>
<name>NormalPluginMessage</name>
<message>
<location filename="SKSEPluginChecker.py" line="52"/>
<source>{0} version {1} ({2}) {3}.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="SKSEPluginChecker.py" line="57"/>
<source>loaded correctly</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="SKSEPluginChecker.py" line="58"/>
<source>reported as incompatible during query</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="SKSEPluginChecker.py" line="59"/>
<source>reported as incompatible during load</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="SKSEPluginChecker.py" line="60"/>
<source>disabled, fatal error occurred while loading plugin</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="SKSEPluginChecker.py" line="61"/>
<source>disabled, no name specified</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="SKSEPluginChecker.py" line="62"/>
<source>disabled, fatal error occurred while checking plugin compatibility</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="SKSEPluginChecker.py" line="63"/>
<source>disabled, fatal error occurred while querying plugin</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>NotAPluginMessage</name>
<message>
<location filename="SKSEPluginChecker.py" line="103"/>
<source>{0} does not appear to be an SKSE plugin.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>PluginMessage</name>
<message>
<location filename="SKSEPluginChecker.py" line="20"/>
<source>{0} existed.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>ScriptExtenderPluginChecker</name>
<message>
<location filename="SKSEPluginChecker.py" line="147"/>
<source>Checks script extender log to see if any plugins failed to load.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="SKSEPluginChecker.py" line="166"/>
<source>Script extender log reports incompatible plugins.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="SKSEPluginChecker.py" line="171"/>
<source>You have one or more script extender plugins which failed to load. They are:{0}</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS>