diff --git a/FlexTools.bat b/FlexTools.bat deleted file mode 100644 index 53ba812..0000000 --- a/FlexTools.bat +++ /dev/null @@ -1,10 +0,0 @@ -@echo off - -REM Assume that the default Python will match FLEx for 32/64 bit. -REM If it doesn't, or it is an unsupported Python version, then -REM add a parameter to the command below to select the right version. -REM Use "py --help" to see the options. - -py FlexTools\FlexTools.py %* - -:END diff --git a/FlexTools.vbs b/FlexTools.vbs deleted file mode 100644 index 3d922b1..0000000 --- a/FlexTools.vbs +++ /dev/null @@ -1,9 +0,0 @@ -' Run the batch file with VBS script to avoid the black console window. -' -' The '0' parameter to Run hides the console window. - -Set WshShell = CreateObject("WScript.Shell") - -WshShell.Run "FlexTools.bat", 0, True - -WshShell = Null diff --git a/FlexTools/Collections/Examples.ini b/FlexTools/Collections/Examples.ini index 1462970..3b72ec1 100644 --- a/FlexTools/Collections/Examples.ini +++ b/FlexTools/Collections/Examples.ini @@ -1,6 +1,20 @@ -[Reports.Text Statistics] +[Reports.Project Information] _order = 1 -[Reports.Lexicon Statistics] +[Reports.Text Statistics] _order = 2 +[Reports.Lexicon Statistics] +_order = 3 + +[Utilities.Approve Spelling of Numbers] +_order = 4 + +[Reports.Lexeme Usage in Corpus] +_order = 5 + +[Examples.Example - Check Punctuation] +_order = 6 + +[Export.Dump All Headwords To File] +_order = 7 diff --git a/FlexTools/FTPaths.py b/FlexTools/FTPaths.py deleted file mode 100644 index 7535ea6..0000000 --- a/FlexTools/FTPaths.py +++ /dev/null @@ -1,16 +0,0 @@ -# -# Project: FlexTools -# Module: FTPaths -# -# Global definitions of data paths for FLExTools -# -# Craig Farrow -# Mar 2012 -# - -import os - -# Create absolute paths relative to this directory -MODULES_PATH = os.path.join(os.path.dirname(__file__), "Modules") -COLLECTIONS_PATH = os.path.join(os.path.dirname(__file__), "Collections") -CONFIG_PATH = os.path.join(os.path.dirname(__file__), "flextools.ini") diff --git a/FlexTools/FlexTools.vbs b/FlexTools/FlexTools.vbs new file mode 100644 index 0000000..7bf6525 --- /dev/null +++ b/FlexTools/FlexTools.vbs @@ -0,0 +1,7 @@ +' Run the FlexTools application + +Set WshShell = CreateObject("WScript.Shell") + +WshShell.Run "scripts\FlexToolsCommands.vbs RUN", 0, True + +WshShell = Null diff --git a/FlexTools/FlexTools_Debug.vbs b/FlexTools/FlexTools_Debug.vbs new file mode 100644 index 0000000..73f5484 --- /dev/null +++ b/FlexTools/FlexTools_Debug.vbs @@ -0,0 +1,7 @@ +' Run FlexTools with debug logging enabled + +Set WshShell = CreateObject("WScript.Shell") + +WshShell.Run "scripts\FlexToolsCommands.vbs DEBUG", 0, True + +WshShell = Null diff --git a/FlexTools/InstallOrUpdate.vbs b/FlexTools/InstallOrUpdate.vbs new file mode 100644 index 0000000..3ee88ab --- /dev/null +++ b/FlexTools/InstallOrUpdate.vbs @@ -0,0 +1,8 @@ +' Run the FlexTools installer (pip) for first install or to update +' to a later version. + +Set WshShell = CreateObject("WScript.Shell") + +WshShell.Run "scripts\FlexToolsCommands.vbs INSTALL", 0, True + +WshShell = Null diff --git a/FlexTools/ListProjects.vbs b/FlexTools/ListProjects.vbs new file mode 100644 index 0000000..e520697 --- /dev/null +++ b/FlexTools/ListProjects.vbs @@ -0,0 +1,7 @@ +' Run the FlexTools application + +Set WshShell = CreateObject("WScript.Shell") + +WshShell.Run "scripts\FlexToolsCommands.vbs LIST", 0, True + +WshShell = Null diff --git a/FlexTools/Modules/Chinese/Doc/Building packages.txt b/FlexTools/Modules/Chinese/Doc/Building packages.txt new file mode 100644 index 0000000..d9ae1b6 --- /dev/null +++ b/FlexTools/Modules/Chinese/Doc/Building packages.txt @@ -0,0 +1,9 @@ +How to build Chinese Tools packages +----------------------------------- + +1. Create Full ZIP for archiving. + 1. Delete all .pyc and .log files from the Chinese tree + 2. Create ZIP file with Chinese.pth + Chinese\ + 3. Name it "ChineseTools-MASTER-.zip" (date in YYYY-MM-DD format) + + diff --git a/FlexTools/Modules/Chinese/Doc/Chinese Utilities Help.doc b/FlexTools/Modules/Chinese/Doc/Chinese Utilities Help.doc new file mode 100644 index 0000000..d02e771 Binary files /dev/null and b/FlexTools/Modules/Chinese/Doc/Chinese Utilities Help.doc differ diff --git a/FlexTools/Modules/Chinese/Generate_Reversal_Sort_Field_Only.py b/FlexTools/Modules/Chinese/Generate_Reversal_Sort_Field_Only.py index 5dd016d..54f037e 100644 --- a/FlexTools/Modules/Chinese/Generate_Reversal_Sort_Field_Only.py +++ b/FlexTools/Modules/Chinese/Generate_Reversal_Sort_Field_Only.py @@ -3,8 +3,9 @@ # Chinese.Generate Reversal Sort Field Only # - A FlexTools Module - # -# Finds the Chinese Reversal Index and uses the Hanzi field to generate the Pinyin Numbered -# field, and then uses those to populate the Sort field. See documentation below for the Writing System +# Finds the Chinese Reversal Index and uses the Hanzi field to +# generate the Pinyin Numbered field, and then uses those to populate +# the Sort field. See documentation below for the Writing System # codes. # # C D Farrow @@ -13,10 +14,7 @@ # Platforms: Python .NET and IronPython # -from __future__ import unicode_literals -from __future__ import print_function -from FTModuleClass import * - +from flextoolslib import * #---------------------------------------------------------------- # Documentation for the user: diff --git a/FlexTools/Modules/Chinese/Update_Pinyin_Fields.py b/FlexTools/Modules/Chinese/Update_Pinyin_Fields.py index 1ce2b50..b4987cc 100644 --- a/FlexTools/Modules/Chinese/Update_Pinyin_Fields.py +++ b/FlexTools/Modules/Chinese/Update_Pinyin_Fields.py @@ -15,7 +15,7 @@ import unicodedata -from FTModuleClass import * +from flextoolslib import * import site site.addsitedir(r"Lib") diff --git a/FlexTools/Modules/Chinese/Update_Reversal_Sort_Field.py b/FlexTools/Modules/Chinese/Update_Reversal_Sort_Field.py index b634187..8702ff7 100644 --- a/FlexTools/Modules/Chinese/Update_Reversal_Sort_Field.py +++ b/FlexTools/Modules/Chinese/Update_Reversal_Sort_Field.py @@ -16,7 +16,7 @@ from __future__ import unicode_literals from builtins import str -from FTModuleClass import * +from flextoolslib import * import site site.addsitedir(r"Lib") diff --git a/FlexTools/Modules/Chinese/Update_Tonenumber_Fields.py b/FlexTools/Modules/Chinese/Update_Tonenumber_Fields.py index 3e6fdb9..701c302 100644 --- a/FlexTools/Modules/Chinese/Update_Tonenumber_Fields.py +++ b/FlexTools/Modules/Chinese/Update_Tonenumber_Fields.py @@ -13,7 +13,7 @@ from __future__ import unicode_literals from builtins import str -from FTModuleClass import * +from flextoolslib import * import site site.addsitedir(r"Lib") diff --git a/FlexTools/Modules/Chinese/__test.py b/FlexTools/Modules/Chinese/__test.py new file mode 100644 index 0000000..3b8aeac --- /dev/null +++ b/FlexTools/Modules/Chinese/__test.py @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- +# +# ChineseUtilities +# +# Craig Farrow +# May 2011 +# +# +# + +from __future__ import unicode_literals +from __future__ import print_function +from builtins import str + +import codecs +import re + +import os, sys + +class SortStringDB(dict): + FNAME = 'ch2sort.txt' + def __init__(self): + dict.__init__(self) + self.loaded = False + + def __load(self): + if self.loaded: return + self.loaded = True # Set True even if fail to load file + # This stack-frame code is all that I've found to work from + # Idle, command-line AND Python.NET + # (Use of __file__ failed for Idle) + mypath = os.path.dirname(sys._getframe().f_code.co_filename) + fname = os.path.join(mypath, self.FNAME) + print(fname) + try: + f = codecs.open(fname, encoding="gb18030") + except IOError: + return + + for line in f.readlines(): + line = line.strip("\n\r") + parts = line.split("\t") + if len(parts) == 2: + self[parts[0]] = parts[1] + else: + ambiguities = {} + hz = parts.pop(0) + while (parts): + py = parts.pop(0) + sortKey = parts.pop(0) + ambiguities[py] = sortKey + self[hz] = ambiguities + + f.close() + + def Lookup(self, hz, py): + self.__load() + try: + sortInfo = self[hz] + except KeyError: + return "[HZ not in DB: %s]" % repr(hz) + if type(sortInfo) == str: + if sortInfo.startswith(py): + return "(" + sortInfo + ")" + else: + return "[PY mismatch]" + else: + try: + match = sortInfo[py] + return "(" + match + ")" + except KeyError: + return "[PY invalid]" + +Sorter = SortStringDB() + +def PinyinTonenumSplit(pinyin): + pinyin = re.sub(" ", "", pinyin) + return re.findall("[^1-5]*[1-5]", pinyin) + +def ChineseSortString(hz, py): + global Sorter + if not hz or not py: + return "" + hzList = [x for x in hz] + pyList = PinyinTonenumSplit(py) + if len(hzList) != len(pyList): + return "[PY different length]" + + return ";".join([Sorter.Lookup(h,p) for h, p in zip(hzList, pyList)]) + + +if __name__ == "__main__": + if sys.stdout.encoding == None: + sys.stdout = codecs.getwriter("utf-8")(sys.stdout) + + testSet = [ + ("路", "lu4"), + ("你好", "ni3hao3"), + ("中国", "zhong1 guo2"), + ("录音", "lu4yin1"), + ("录", ""), + ("孩子", "hai2zi5"), + ] + + for t in testSet: + print(t[0], t[1], ChineseSortString(t[0], t[1])) + + + + diff --git a/FlexTools/Modules/Duplicates/Find_Duplicate_Definitions.py b/FlexTools/Modules/Duplicates/Find_Duplicate_Definitions.py index 0317f7a..3e0d865 100644 --- a/FlexTools/Modules/Duplicates/Find_Duplicate_Definitions.py +++ b/FlexTools/Modules/Duplicates/Find_Duplicate_Definitions.py @@ -14,10 +14,7 @@ # Platforms: Python .NET and IronPython # -from __future__ import unicode_literals -from builtins import str - -from FTModuleClass import * +from flextoolslib import * import re from types import * diff --git a/FlexTools/Modules/Duplicates/Find_Duplicate_Entries.py b/FlexTools/Modules/Duplicates/Find_Duplicate_Entries.py index 1c6bcf5..920b8a3 100644 --- a/FlexTools/Modules/Duplicates/Find_Duplicate_Entries.py +++ b/FlexTools/Modules/Duplicates/Find_Duplicate_Entries.py @@ -16,9 +16,8 @@ # Platforms: Python .NET and IronPython # -from __future__ import unicode_literals +from flextoolslib import * -from FTModuleClass import * from SIL.LCModel import * from SIL.LCModel import MoMorphTypeTags @@ -27,10 +26,6 @@ from collections import defaultdict from types import * - - -# from SIL.FieldWorks.FDO import MoMorphTypeTags - #---------------------------------------------------------------- # Documentation that the user sees: diff --git a/FlexTools/Modules/Duplicates/Merge_Entries.py b/FlexTools/Modules/Duplicates/Merge_Entries.py index 0447d1e..001b5c2 100644 --- a/FlexTools/Modules/Duplicates/Merge_Entries.py +++ b/FlexTools/Modules/Duplicates/Merge_Entries.py @@ -20,9 +20,8 @@ # Platforms: Python.NET # -from __future__ import unicode_literals +from flextoolslib import * -from FTModuleClass import * from SIL.LCModel import * from SIL.LCModel.Core.KernelInterfaces import ITsString, ITsStrBldr diff --git a/FlexTools/Modules/Duplicates/Merge_Senses.py b/FlexTools/Modules/Duplicates/Merge_Senses.py index 5227805..9a4a705 100644 --- a/FlexTools/Modules/Duplicates/Merge_Senses.py +++ b/FlexTools/Modules/Duplicates/Merge_Senses.py @@ -11,9 +11,8 @@ # Platforms: Python.NET # -from __future__ import unicode_literals +from flextoolslib import * -from FTModuleClass import * from SIL.LCModel import * from SIL.LCModel.Core.KernelInterfaces import ITsString, ITsStrBldr @@ -22,7 +21,6 @@ from collections import defaultdict from types import * - #---------------------------------------------------------------- # Documentation that the user sees: diff --git a/FlexTools/Modules/Examples/Example_Check_Punctuation.py b/FlexTools/Modules/Examples/Example_Check_Punctuation.py index 1db90a1..55746f1 100644 --- a/FlexTools/Modules/Examples/Example_Check_Punctuation.py +++ b/FlexTools/Modules/Examples/Example_Check_Punctuation.py @@ -18,9 +18,7 @@ # Platforms: Python .NET and IronPython # -from __future__ import unicode_literals - -from FTModuleClass import * +from flextoolslib import * import re from types import * diff --git a/FlexTools/Modules/Examples/Example_Programming_Errors.py b/FlexTools/Modules/Examples/Example_Programming_Errors.py index 06091ff..01d77fa 100644 --- a/FlexTools/Modules/Examples/Example_Programming_Errors.py +++ b/FlexTools/Modules/Examples/Example_Programming_Errors.py @@ -11,9 +11,7 @@ # Platforms: Python .NET # -from __future__ import unicode_literals - -from FTModuleClass import * +from flextoolslib import * import logging logger = logging.getLogger(__name__) diff --git a/FlexTools/Modules/Examples/__Template.py b/FlexTools/Modules/Examples/__Template.py index cc50da4..3203dd1 100644 --- a/FlexTools/Modules/Examples/__Template.py +++ b/FlexTools/Modules/Examples/__Template.py @@ -10,7 +10,7 @@ # Platforms: Python .NET and IronPython # -from FTModuleClass import * +from flextoolslib import * #---------------------------------------------------------------- diff --git a/FlexTools/Modules/Export/Dump_All_Headwords_To_File.py b/FlexTools/Modules/Export/Dump_All_Headwords_To_File.py index 8b1ae71..e7cc820 100644 --- a/FlexTools/Modules/Export/Dump_All_Headwords_To_File.py +++ b/FlexTools/Modules/Export/Dump_All_Headwords_To_File.py @@ -11,9 +11,7 @@ # Platforms: Python .NET and IronPython # -from __future__ import unicode_literals - -from FTModuleClass import * +from flextoolslib import * import io #---------------------------------------------------------------- diff --git a/FlexTools/Modules/Export/Dump_Published_Headwords_To_File.py b/FlexTools/Modules/Export/Dump_Published_Headwords_To_File.py index 121a2b9..e5047bc 100644 --- a/FlexTools/Modules/Export/Dump_Published_Headwords_To_File.py +++ b/FlexTools/Modules/Export/Dump_Published_Headwords_To_File.py @@ -11,9 +11,7 @@ # Platforms: Python .NET and IronPython # -from __future__ import unicode_literals - -from FTModuleClass import * +from flextoolslib import * import io #---------------------------------------------------------------- diff --git a/FlexTools/Modules/Export/Dump_SemanticDomains_To_File.py b/FlexTools/Modules/Export/Dump_SemanticDomains_To_File.py index 89e51f9..5fd0021 100644 --- a/FlexTools/Modules/Export/Dump_SemanticDomains_To_File.py +++ b/FlexTools/Modules/Export/Dump_SemanticDomains_To_File.py @@ -11,9 +11,7 @@ # Platforms: Python .NET and IronPython # -from __future__ import unicode_literals - -from FTModuleClass import * +from flextoolslib import * import io #---------------------------------------------------------------- diff --git a/FlexTools/Modules/Export/Dump_Texts_To_File.py b/FlexTools/Modules/Export/Dump_Texts_To_File.py index 418ab66..29e1a93 100644 --- a/FlexTools/Modules/Export/Dump_Texts_To_File.py +++ b/FlexTools/Modules/Export/Dump_Texts_To_File.py @@ -11,9 +11,7 @@ # Platforms: Python .NET and IronPython # -from __future__ import unicode_literals - -from FTModuleClass import * +from flextoolslib import * import io #---------------------------------------------------------------- diff --git a/FlexTools/Modules/Integrity Checks/Check_Count_of_Lexemes_in_Corpus.py b/FlexTools/Modules/Integrity Checks/Check_Count_of_Lexemes_in_Corpus.py index 705b843..1b63587 100644 --- a/FlexTools/Modules/Integrity Checks/Check_Count_of_Lexemes_in_Corpus.py +++ b/FlexTools/Modules/Integrity Checks/Check_Count_of_Lexemes_in_Corpus.py @@ -14,9 +14,8 @@ # Platforms: Python.NET and IronPython # -from __future__ import unicode_literals +from flextoolslib import * -from FTModuleClass import * from SIL.LCModel import * from collections import defaultdict diff --git a/FlexTools/Modules/Reports/Incomplete_Analyses.py b/FlexTools/Modules/Reports/Incomplete_Analyses.py index 39c4de3..07ac48a 100644 --- a/FlexTools/Modules/Reports/Incomplete_Analyses.py +++ b/FlexTools/Modules/Reports/Incomplete_Analyses.py @@ -9,15 +9,13 @@ # Platforms: Python.NET and IronPython # -from __future__ import unicode_literals +from flextoolslib import * -from FTModuleClass import * from SIL.LCModel import * from SIL.LCModel.Core.KernelInterfaces import ITsString, ITsStrBldr from collections import defaultdict - #---------------------------------------------------------------- # Documentation that the user sees: diff --git a/FlexTools/Modules/Reports/Lexeme_Usage_In_Corpus.py b/FlexTools/Modules/Reports/Lexeme_Usage_In_Corpus.py index cb2711a..d6a2076 100644 --- a/FlexTools/Modules/Reports/Lexeme_Usage_In_Corpus.py +++ b/FlexTools/Modules/Reports/Lexeme_Usage_In_Corpus.py @@ -17,9 +17,8 @@ # Platforms: Python.NET # -from __future__ import unicode_literals +from flextoolslib import * -from FTModuleClass import * from SIL.LCModel import * from SIL.LCModel.Core.KernelInterfaces import ITsString, ITsStrBldr diff --git a/FlexTools/Modules/Reports/Lexicon_Statistics.py b/FlexTools/Modules/Reports/Lexicon_Statistics.py index 2a12f44..caf1c56 100644 --- a/FlexTools/Modules/Reports/Lexicon_Statistics.py +++ b/FlexTools/Modules/Reports/Lexicon_Statistics.py @@ -15,9 +15,7 @@ # Platforms: Python .NET and IronPython # -from __future__ import unicode_literals - -from FTModuleClass import * +from flextoolslib import * #---------------------------------------------------------------- # Documentation that the user sees: diff --git a/FlexTools/Modules/Reports/Project_Information.py b/FlexTools/Modules/Reports/Project_Information.py index 86b9703..5255c80 100644 --- a/FlexTools/Modules/Reports/Project_Information.py +++ b/FlexTools/Modules/Reports/Project_Information.py @@ -10,9 +10,7 @@ # Platforms: Python .NET # -from __future__ import unicode_literals - -from FTModuleClass import * +from flextoolslib import * #---------------------------------------------------------------- # Documentation that the user sees: diff --git a/FlexTools/Modules/Reports/Text_Statistics.py b/FlexTools/Modules/Reports/Text_Statistics.py index fd0c72f..585c9b9 100644 --- a/FlexTools/Modules/Reports/Text_Statistics.py +++ b/FlexTools/Modules/Reports/Text_Statistics.py @@ -14,9 +14,7 @@ # Platforms: Python .NET and IronPython # -from __future__ import unicode_literals - -from FTModuleClass import * +from flextoolslib import * #---------------------------------------------------------------- # Documentation that the user sees: diff --git a/FlexTools/Modules/Utilities/Approve_Spelling_of_Numbers.py b/FlexTools/Modules/Utilities/Approve_Spelling_of_Numbers.py index 0909c85..2b2ccc1 100644 --- a/FlexTools/Modules/Utilities/Approve_Spelling_of_Numbers.py +++ b/FlexTools/Modules/Utilities/Approve_Spelling_of_Numbers.py @@ -13,7 +13,8 @@ # Platforms: Python.NET # -from FTModuleClass import * +from flextoolslib import * + from SIL.LCModel import * from SIL.LCModel.Core.KernelInterfaces import ITsString, ITsStrBldr from SIL.LCModel import SpellingStatusStates @@ -21,7 +22,6 @@ import re from types import * - #---------------------------------------------------------------- # Configurables: diff --git a/FlexTools/Modules/readme.md b/FlexTools/Modules/readme.md index 3ae8eb5..e17b9dc 100644 --- a/FlexTools/Modules/readme.md +++ b/FlexTools/Modules/readme.md @@ -14,7 +14,7 @@ Imports ------- ```python -from FTModuleClass import * +from flextoolslib import * #(These are needed for string operations) from SIL.LCModel.Core.KernelInterfaces import ITsString, ITsStrBldr diff --git a/FlexTools/Version.py b/FlexTools/Version.py deleted file mode 100644 index 524fefa..0000000 --- a/FlexTools/Version.py +++ /dev/null @@ -1,12 +0,0 @@ -# -# Version.py -# -# Current version number for FLExTools -# - -number = "2.1.2" - -# Minimum and maximum supported versions of Fieldworks -# (Later versions should work if the LCM interface hasn't changed.) -MinFWVersion = "9.0.4" -MaxFWVersion = "9.1.12" diff --git a/FlexTools/_TestAModule.py b/FlexTools/_TestAModule.py deleted file mode 100644 index 29d3929..0000000 --- a/FlexTools/_TestAModule.py +++ /dev/null @@ -1,145 +0,0 @@ -# -# _TestAModule -# -# Test frame for developing a new Module. Run as a command-line application: -# python _TestAModule -# - -import os -import sys -import imp -# import importlib - TODO: switch to using importlib -import traceback - -import logging -logging.basicConfig( - stream=sys.stdout, - # filename='_TestAModule.log', filemode='w', - level=logging.DEBUG) - -logger = logging.getLogger("_TestAModule") - -from flexlibs import FLExInitialize, FLExCleanup -from flexlibs import ( - FLExProject, - FP_ProjectError, - FP_FileNotFoundError) - -from FTModuleClass import FTM_ModuleError -import FTReport -import FTPaths - - -#---------------------------------------------------------------- - -def importModule(moduleFolderAndName): - # - # Import the module - # - - path, moduleName = os.path.split(moduleFolderAndName) - moduleName, ext = os.path.splitext(moduleName) - libPath = os.path.join(FTPaths.MODULES_PATH, path) - absPath = os.path.abspath(os.path.join(FTPaths.MODULES_PATH, - moduleFolderAndName)) - logger.debug(absPath) - - # Append the local path in case there are local dependencies - sys.path.append(libPath) - - logger.info("Importing %s::%s" % (libPath, moduleName)) - - try: - fp, pathname, description = imp.find_module(moduleName, [libPath]) - except ImportError as e: - logger.error("Can't find module '%s!'" % moduleFolderAndName) - return None - - try: - module = imp.load_module(moduleName, fp, pathname, description) - except FTM_ModuleError as e: - msg = "%s\\%s:\n%s" % (path, moduleName, e.message) - logger.error(msg) - return None - except: - msg = "Module error: %s\n %s" % (pathname, traceback.format_exc()) - logger.error(msg) - return None - finally: - # Since we may exit via an exception, close fp explicitly. - if fp: - fp.close() - - try: - ftm = module.FlexToolsModule - except AttributeError: - logger.error ("FlexToolsModule not found in %s." \ - % moduleFolderAndName) - return None - - return ftm - -#---------------------------------------------------------------- -def usage(): - print ("USAGE: _TestAModule ") - - -#---------------------------------------------------------------- - -def main(module, project): - - # --- Import the module --- - ftm = importModule(module) - if not ftm: - usage() - return - - logger.info ("Module info:\n%s", ftm.Help()) - - # --- Open the project --- - logger.info("Opening project '%s'" % project) - FlexDB = FLExProject() - - try: - FlexDB.OpenProject(projectName = project) - logger.info("OK") - except FP_FileNotFoundError as e: - logger.error("Project '%s' not found!" % project) - return - except FP_ProjectError as e: - logger.error("Error opening project!") - return - - # --- Run the module --- - reporter = FTReport.FTReporter() - try: - ftm.Run(FlexDB, reporter) - except: - logger.exception("Runtime error:") - return - - rLOOKUP = ["INFO", "WARN", "ERR ", " "] - for m in reporter.messages: - t, msg, extra = m - logger.info ("%s: %s" % (rLOOKUP[t], msg)) - if extra: - logger.info (">>>> %s" % extra) - - FlexDB.CloseProject() - - -if __name__ == "__main__": - - if len(sys.argv) != 3: - usage() - sys.exit(1) - - - ModuleToTest = sys.argv[1] - ProjectName = sys.argv[2] - - FLExInitialize() - - main(ModuleToTest, ProjectName) - - FLExCleanup() diff --git a/FlexTools/scripts/FlexToolsCommands.vbs b/FlexTools/scripts/FlexToolsCommands.vbs new file mode 100644 index 0000000..7f9e9ff --- /dev/null +++ b/FlexTools/scripts/FlexToolsCommands.vbs @@ -0,0 +1,71 @@ +' +' FlexToolsCommands.vbs +' +' Copyright Craig Farrow, 2022 +' +' A collection of commands for running FlexTools. +' These are gathered here, so there is only one place to specify +' which version of Python to use. +' Additionally, we are using VBS so we can use the '0' parameter to +' Run, which hides the console window. +' +' Usage: +' FlexToolsCommands.vbs () +' +' The commands are (case insensitive): +' Run - run the program +' Debug - run FlexTools with debugging output +' Install - run pip to install or upgrade flextoolslib +' List - output a list of all the FieldWorks projects +' + +PYTHON = "py -3.8" + +FLEXTOOLS = PYTHON & " .\scripts\RunFlexTools.py" + + +' The ini file name/path can be supplied as an argument. +If WScript.Arguments.Count > 1 Then + FLEXTOOLS = FLEXTOOLS & " " & WScript.Arguments.Item(1) +End If + + +Set WshShell = CreateObject("WScript.Shell") + +func = GetRef("Do" & WScript.Arguments.Item(0)) + +WshShell = Null + +'----------------------------------------------------------- +Function ErrorMsg() + MsgBox("Error running FlexTools with command '" & PYTHON & "'. Please run InstallOrUpdate.vbs, then try again.") +End Function + +Function DoRun() + rc = WshShell.Run(FLEXTOOLS, 0, True) + If rc <> 0 Then ErrorMsg +End Function + +Function DoDebug() + Set fso = CreateObject("Scripting.FileSystemObject") + If fso.FileExists("flextools.log") Then + fso.DeleteFile("flextools.log") + End If + rc = WshShell.Run(FLEXTOOLS & " DEBUG", 0, True) + If rc <> 0 Then ErrorMsg + If fso.FileExists("flextools.log") Then + WshShell.Run("notepad flextools.log") + End If +End Function + +Function DoInstall() + ' Use CMD so we can do a pause to keep the output visible. + rc = WshShell.Run("%comspec% /c """&PYTHON&" -m pip install --upgrade -r scripts\requirements.txt & pause""", 1, True) +End Function + +Function DoList() + ' Use CMD so we can do a pause to keep the output visible. + rc = WshShell.Run("%comspec% /c """&PYTHON&" .\scripts\ListProjects.py & pause""", 1, True) +End Function +'----------------------------------------------------------- + diff --git a/FlexTools/_ListProjects.py b/FlexTools/scripts/ListProjects.py similarity index 94% rename from FlexTools/_ListProjects.py rename to FlexTools/scripts/ListProjects.py index 132f951..e1a04a1 100644 --- a/FlexTools/_ListProjects.py +++ b/FlexTools/scripts/ListProjects.py @@ -1,5 +1,5 @@ # -# _ListProjects +# ListProjects # # List the Fieldworks projects on this system. # diff --git a/FlexTools/scripts/RunFlexTools.py b/FlexTools/scripts/RunFlexTools.py new file mode 100644 index 0000000..a10aae2 --- /dev/null +++ b/FlexTools/scripts/RunFlexTools.py @@ -0,0 +1,9 @@ +# +# A shell module to launch FlexTools +# + +from Version import FlexToolsVersion + +import flextoolslib + +flextoolslib.main(FlexToolsVersion) diff --git a/FlexTools/scripts/TestAModule.py b/FlexTools/scripts/TestAModule.py new file mode 100644 index 0000000..45c6eb4 --- /dev/null +++ b/FlexTools/scripts/TestAModule.py @@ -0,0 +1,48 @@ +# +# TestAModule +# +# Test frame for developing a new Module. +# Run as a command-line application: +# py TestAModule.py +# + +import sys + + +LOG_FILE = "TestAModule.log" + +import logging +logging.basicConfig(filename=LOG_FILE, + filemode='w', + level=logging.DEBUG) + +logger = logging.getLogger(__name__) + + +from flextoolslib import RunModule + + +#---------------------------------------------------------------- +def usage(): + print ("USAGE: TestAModule ") + +#---------------------------------------------------------------- + +if __name__ == "__main__": + + if len(sys.argv) != 3: + usage() + sys.exit(1) + + ModuleToTest = sys.argv[1] + ProjectName = sys.argv[2] + + if RunModule(ModuleToTest, ProjectName): + print ("Success!") + else: + print ("Failed!") + + print (f"=== {LOG_FILE} ===") + + with open(LOG_FILE, 'r') as f: + print(f.read()) diff --git a/FlexTools/scripts/Version.py b/FlexTools/scripts/Version.py new file mode 100644 index 0000000..93dc19b --- /dev/null +++ b/FlexTools/scripts/Version.py @@ -0,0 +1,5 @@ +# +# +# + +FlexToolsVersion = "2.2.0" diff --git a/FlexTools/scripts/requirements.txt b/FlexTools/scripts/requirements.txt new file mode 100644 index 0000000..3cd5f5b --- /dev/null +++ b/FlexTools/scripts/requirements.txt @@ -0,0 +1,4 @@ +# Runtime requirements for FlexTools +cdfutils >= 1.0.4 +flexlibs >= 1.1.6 +flextoolslib diff --git a/FlexTools_Debug.bat b/FlexTools_Debug.bat deleted file mode 100644 index f89642f..0000000 --- a/FlexTools_Debug.bat +++ /dev/null @@ -1,5 +0,0 @@ -@echo off - -call FlexTools.bat DEBUG - -notepad flextools.log diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..97faffd --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,472 @@ +Copyright (c) 2008-2022 Craig Farrow +This software is licensed under the LGPL, version 2.1 or later +(http://www.gnu.org/licenses/lgpl-2.1.html) + +This software is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This software is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..0b7ce4e --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +graft flextoolslib\docs +graft flextoolslib\icons diff --git a/docs/FLExTools Help.pdf b/docs/FLExTools Help.pdf deleted file mode 100644 index b6db860..0000000 Binary files a/docs/FLExTools Help.pdf and /dev/null differ diff --git a/docs/FLExTools Programming.pdf b/docs/FLExTools Programming.pdf deleted file mode 100644 index 73b12fc..0000000 Binary files a/docs/FLExTools Programming.pdf and /dev/null differ diff --git a/docs/src/FLExTools Help.doc b/docs/src/FLExTools Help.doc index d224f52..c17474c 100644 Binary files a/docs/src/FLExTools Help.doc and b/docs/src/FLExTools Help.doc differ diff --git a/docs/src/FLExTools Programming.doc b/docs/src/FLExTools Programming.doc index c461f21..3f5abaf 100644 Binary files a/docs/src/FLExTools Programming.doc and b/docs/src/FLExTools Programming.doc differ diff --git a/flextoolslib/__init__.py b/flextoolslib/__init__.py new file mode 100644 index 0000000..09eb571 --- /dev/null +++ b/flextoolslib/__init__.py @@ -0,0 +1,36 @@ +#---------------------------------------------------------------------------- +# Name: __init__.py +# Purpose: flextools is a GUI tool for running Python scripts on +# FieldWorks Language Explorer projects. +#---------------------------------------------------------------------------- + +version = "2023.1.31" + +# Minimum and maximum supported versions of Fieldworks +# (Later versions should work if the LCM interface hasn't changed.) +MinFWVersion = "9.0.4" +MaxFWVersion = "9.1.18" + + +# Define exported classes, etc. at the top level of the package + +# The main application +from .code.FLExTools import ( + main, + ) + +# The Modules class and documentation constants +from .code.FTModuleClass import ( + FlexToolsModuleClass, + FTM_Name, + FTM_Version, + FTM_ModifiesDB, + FTM_Synopsis, + FTM_Help, + FTM_Description, + ) + +# Expose RunModule for testing purposes (see TestAModule.py) +from .misc.RunModule import ( + RunModule, + ) diff --git a/flextoolslib/code/FLExTools.py b/flextoolslib/code/FLExTools.py new file mode 100644 index 0000000..47890a4 --- /dev/null +++ b/flextoolslib/code/FLExTools.py @@ -0,0 +1,120 @@ +# +# Project: FlexTools +# Module: FLExTools +# Platform: .NET v2 Windows.Forms (Python.NET 2.7) +# +# The main entry point for the FlexTools application is here as main(). +# It is called from flextools.exe, which is created during the build +# process. +# +# First, set up the following: +# - logging +# - configuration parameters and paths (flextools.ini) +# - load flexlibs and handle any errors +# Then, in main() +# - initialise the Flex interface (flexlibs) +# - launch the FlexTools UI +# +# Copyright Craig Farrow, 2010 - 2022 +# + +import sys +import os +import traceback + + +# ----------------------------------------------------------- +# Imports +# ----------------------------------------------------------- + +# This call is required to initialise the threading mode for COM calls +# (e.g. using the clipboard) It must be made before clr is imported. +import ctypes +ctypes.windll.ole32.CoInitialize(None) + + +import clr +import System + +clr.AddReference("System.Windows.Forms") +from System.Windows.Forms import Application +from System.Windows.Forms import ( + MessageBox, + MessageBoxButtons, + MessageBoxIcon) + +# ----------------------------------------------------------- +# Logging +# ----------------------------------------------------------- + +import logging + +if (sys.argv[-1].lower() == "debug"): + loggingLevel = logging.DEBUG +else: + loggingLevel = logging.INFO + +logging.basicConfig(filename='flextools.log', + filemode='w', + level=loggingLevel) + +logger = logging.getLogger(__name__) + + +# ----------------------------------------------------------- +# Paths and configuration +# ----------------------------------------------------------- + +from .. import version +logger.info(f"FLExTools library version: {version}") + +from .FTConfig import FTConfig + +#----------------------------------------------------------- +# flexlibs +#----------------------------------------------------------- + +from .. import MinFWVersion, MaxFWVersion + +try: + from flexlibs import FLExInitialize, FLExCleanup + +except Exception as e: + msg = f"There was an issue during initialisation.\n{e}\n" \ + f"(This version of FLExTools has been tested with Fieldworks versions {MinFWVersion} - {MaxFWVersion}.)\n" \ + f"See flextools.log for more details." + logger.error(msg) + MessageBox.Show(msg, + "FLExTools: Fatal Error", + MessageBoxButtons.OK, + MessageBoxIcon.Exclamation) + logger.error("Fatal exception importing flexlibs:\n%s" % traceback.format_exc()) + sys.exit(1) + +logger.info(f"flexlibs imported successfully") + + +#----------------------------------------------------------- +# UI +#----------------------------------------------------------- + +from .UIMain import FTMainForm + + +# ------------------------------------------------------------------ +def main(appVersion=""): + global FTConfig + + FLExInitialize() + + logger.debug("Creating MainForm") + form = FTMainForm(appVersion) + logger.debug("Launching WinForms Application") + Application.Run(form) + + # Save the configuration + FTConfig.save() + + FLExCleanup() + + diff --git a/FlexTools/FTCollections.py b/flextoolslib/code/FTCollections.py similarity index 96% rename from FlexTools/FTCollections.py rename to flextoolslib/code/FTCollections.py index 3042439..bd40aae 100644 --- a/FlexTools/FTCollections.py +++ b/flextoolslib/code/FTCollections.py @@ -1,198 +1,199 @@ - -# -# Project: FlexTools -# Module: FTCollections -# -# Manages user-defined collections of modules. -# - Loads and saves configuration from disk. -# - Provides interface for UI manipulation of collections. -# -# Craig Farrow -# Oct 2009 -# v0.01 -# - -from __future__ import unicode_literals -from builtins import str - -import os - -from configparser import ConfigParser - -from FTPaths import COLLECTIONS_PATH - -# ---- Exceptions ---- - -class Error(Exception): - """Base class for exceptions in this module.""" - pass - -class FTC_NameError(Error): - """ - Exception raised for looking up a collection name that doesn't exist. - - Attributes: - message -- explanation of the error - """ - - def __init__(self, message): - self.message = message - -class FTC_ExistsError(Error): - """ - Exception raised for adding or renaming to a collection name - that is already in use. - - Attributes: - message -- explanation of the error - """ - - def __init__(self, message): - self.message = message - -class FTC_BadNameError(Error): - """ - Exception raised for a name that isn't valid. - - Attributes: - message -- explanation of the error - """ - - def __init__(self, message): - self.message = message -# --------------------------------------------------------------------- -class CollectionsManager(object): - - ORDER_OPTION = "_Order" - COLLECTIONS_SUFFIX = ".ini" - assert(len(COLLECTIONS_SUFFIX) == 4) - - def __init__(self): - # Load all the collection info - - # If the path to os.listdir is unicode, then the file names will - # be correctly decoded and returned as unicode. - collectionNames = [f for f in os.listdir(COLLECTIONS_PATH) - if f.endswith(self.COLLECTIONS_SUFFIX)] - - self.collectionsConfig = {} - - for collectionName in collectionNames: - cp = ConfigParser(interpolation=None) - if cp.read(os.path.join(COLLECTIONS_PATH, collectionName)): - self.collectionsConfig[collectionName[:-4]] = cp # Strip '.ini' - else: - print("Failed to read", collectionName) - - # ---- Access ---- - - def Names(self): - return (list(self.collectionsConfig.keys())) - - def ListOfModules(self, collectionName): - if collectionName not in self.collectionsConfig: - raise FTC_NameError("Bad collection name '%s'" % collectionName) - cp = self.collectionsConfig[collectionName] - modules = cp.sections() - sortedModules = [0] * len(modules) - for moduleName in modules: - try: - order = cp.getint(moduleName, self.ORDER_OPTION) - except: - # Error in collections file; skip this entry - continue - sortedModules[order-1] = moduleName - return (sortedModules) - - def ModuleConfiguration(self, collectionName, moduleName): - return (self.collectionsConfig[collectionName].options(moduleName)) - - # ---- Creating and Modifying ---- - - def Add(self, collectionName): - if collectionName in self.collectionsConfig: - raise FTC_ExistsError(collectionName + " already exists.") - cp = ConfigParser(interpolation=None) - self.collectionsConfig[collectionName] = cp - self.WriteOne(collectionName, cp) - return - - def Delete(self, collectionName): - if collectionName not in self.collectionsConfig: - raise FTC_NameError("Collection not found.") - try: - os.remove(os.path.join(COLLECTIONS_PATH, - collectionName + self.COLLECTIONS_SUFFIX)) - except: - pass - del(self.collectionsConfig[collectionName]) - return - - def Rename(self, collectionName, newName): - if collectionName not in self.collectionsConfig: - raise FTC_NameError("Collection not found.") - if newName in self.collectionsConfig: - raise FTC_ExistsError("'" + newName + "' already exists.") - - try: - os.rename(os.path.join(COLLECTIONS_PATH, - collectionName + self.COLLECTIONS_SUFFIX), - os.path.join(COLLECTIONS_PATH, - newName + self.COLLECTIONS_SUFFIX)) - except: - raise FTC_BadNameError("Error occured renaming collection file. Check that the name is a valid file name.") - else: - self.collectionsConfig[newName] = self.collectionsConfig.pop(collectionName) - - - def AddModule(self, collectionName, moduleName, configuration=[]): - cp = self.collectionsConfig[collectionName] - if cp.has_section(moduleName): - raise FTC_ExistsError(moduleName + " already exists.") - cp.add_section(moduleName) - cp.set(moduleName, self.ORDER_OPTION, str(len(cp.sections()))) - for configItem in configuration: - cp.set(moduleName, configItem.Name, configItem.Default) - return - - def RemoveModule(self, collectionName, moduleName): - cp = self.collectionsConfig[collectionName] - order = cp.getint(moduleName, self.ORDER_OPTION) - cp.remove_section(moduleName) - for m in cp.sections(): - this_order = cp.getint(m, self.ORDER_OPTION) - if this_order > order: - cp.set(m, self.ORDER_OPTION, str(this_order - 1)) - - def MoveModuleUp(self, collectionName, moduleName): - cp = self.collectionsConfig[collectionName] - order = cp.getint(moduleName, self.ORDER_OPTION) - if order > 1: - for m in cp.sections(): - this_order = cp.getint(m, self.ORDER_OPTION) - if this_order == order - 1: - cp.set(m, self.ORDER_OPTION, str(order)) - cp.set(moduleName, self.ORDER_OPTION, str(order - 1)) - - def MoveModuleDown(self, collectionName, moduleName): - cp = self.collectionsConfig[collectionName] - order = cp.getint(moduleName, self.ORDER_OPTION) - if order < len(cp.sections()): - for m in cp.sections(): - this_order = cp.getint(m, self.ORDER_OPTION) - if this_order == order + 1: - cp.set(m, self.ORDER_OPTION, str(order)) - cp.set(moduleName, self.ORDER_OPTION, str(order + 1)) - - # --------- - - def WriteAll(self): - for (name, cp) in self.collectionsConfig.items(): - self.WriteOne(name, cp) - - def WriteOne(self, name, cp): - #print "Writing:", name, cp.sections() - f = open(os.path.join(COLLECTIONS_PATH, - name + self.COLLECTIONS_SUFFIX), "w") - cp.write(f) - f.close() + +# +# Project: FlexTools +# Module: FTCollections +# +# Manages user-defined collections of modules. +# - Loads and saves configuration from disk. +# - Provides interface for UI manipulation of collections. +# +# Craig Farrow +# Oct 2009 +# v0.01 +# + +from __future__ import unicode_literals +from builtins import str + +import os + +from configparser import ConfigParser + +from .FTConfig import FTConfig +COLLECTIONS_PATH = FTConfig.CollectionsPath + +# ---- Exceptions ---- + +class Error(Exception): + """Base class for exceptions in this module.""" + pass + +class FTC_NameError(Error): + """ + Exception raised for looking up a collection name that doesn't exist. + + Attributes: + message -- explanation of the error + """ + + def __init__(self, message): + self.message = message + +class FTC_ExistsError(Error): + """ + Exception raised for adding or renaming to a collection name + that is already in use. + + Attributes: + message -- explanation of the error + """ + + def __init__(self, message): + self.message = message + +class FTC_BadNameError(Error): + """ + Exception raised for a name that isn't valid. + + Attributes: + message -- explanation of the error + """ + + def __init__(self, message): + self.message = message +# --------------------------------------------------------------------- +class CollectionsManager(object): + + ORDER_OPTION = "_Order" + COLLECTIONS_SUFFIX = ".ini" + assert(len(COLLECTIONS_SUFFIX) == 4) + + def __init__(self): + # Load all the collection info + + # If the path to os.listdir is unicode, then the file names will + # be correctly decoded and returned as unicode. + collectionNames = [f for f in os.listdir(COLLECTIONS_PATH) + if f.endswith(self.COLLECTIONS_SUFFIX)] + + self.collectionsConfig = {} + + for collectionName in collectionNames: + cp = ConfigParser(interpolation=None) + if cp.read(os.path.join(COLLECTIONS_PATH, collectionName)): + self.collectionsConfig[collectionName[:-4]] = cp # Strip '.ini' + else: + print("Failed to read", collectionName) + + # ---- Access ---- + + def Names(self): + return (list(self.collectionsConfig.keys())) + + def ListOfModules(self, collectionName): + if collectionName not in self.collectionsConfig: + raise FTC_NameError("Bad collection name '%s'" % collectionName) + cp = self.collectionsConfig[collectionName] + modules = cp.sections() + sortedModules = [0] * len(modules) + for moduleName in modules: + try: + order = cp.getint(moduleName, self.ORDER_OPTION) + except: + # Error in collections file; skip this entry + continue + sortedModules[order-1] = moduleName + return (sortedModules) + + def ModuleConfiguration(self, collectionName, moduleName): + return (self.collectionsConfig[collectionName].options(moduleName)) + + # ---- Creating and Modifying ---- + + def Add(self, collectionName): + if collectionName in self.collectionsConfig: + raise FTC_ExistsError(collectionName + " already exists.") + cp = ConfigParser(interpolation=None) + self.collectionsConfig[collectionName] = cp + self.WriteOne(collectionName, cp) + return + + def Delete(self, collectionName): + if collectionName not in self.collectionsConfig: + raise FTC_NameError("Collection not found.") + try: + os.remove(os.path.join(COLLECTIONS_PATH, + collectionName + self.COLLECTIONS_SUFFIX)) + except: + pass + del(self.collectionsConfig[collectionName]) + return + + def Rename(self, collectionName, newName): + if collectionName not in self.collectionsConfig: + raise FTC_NameError("Collection not found.") + if newName in self.collectionsConfig: + raise FTC_ExistsError("'" + newName + "' already exists.") + + try: + os.rename(os.path.join(COLLECTIONS_PATH, + collectionName + self.COLLECTIONS_SUFFIX), + os.path.join(COLLECTIONS_PATH, + newName + self.COLLECTIONS_SUFFIX)) + except: + raise FTC_BadNameError("Error occured renaming collection file. Check that the name is a valid file name.") + else: + self.collectionsConfig[newName] = self.collectionsConfig.pop(collectionName) + + + def AddModule(self, collectionName, moduleName, configuration=[]): + cp = self.collectionsConfig[collectionName] + if cp.has_section(moduleName): + raise FTC_ExistsError(moduleName + " already exists.") + cp.add_section(moduleName) + cp.set(moduleName, self.ORDER_OPTION, str(len(cp.sections()))) + for configItem in configuration: + cp.set(moduleName, configItem.Name, configItem.Default) + return + + def RemoveModule(self, collectionName, moduleName): + cp = self.collectionsConfig[collectionName] + order = cp.getint(moduleName, self.ORDER_OPTION) + cp.remove_section(moduleName) + for m in cp.sections(): + this_order = cp.getint(m, self.ORDER_OPTION) + if this_order > order: + cp.set(m, self.ORDER_OPTION, str(this_order - 1)) + + def MoveModuleUp(self, collectionName, moduleName): + cp = self.collectionsConfig[collectionName] + order = cp.getint(moduleName, self.ORDER_OPTION) + if order > 1: + for m in cp.sections(): + this_order = cp.getint(m, self.ORDER_OPTION) + if this_order == order - 1: + cp.set(m, self.ORDER_OPTION, str(order)) + cp.set(moduleName, self.ORDER_OPTION, str(order - 1)) + + def MoveModuleDown(self, collectionName, moduleName): + cp = self.collectionsConfig[collectionName] + order = cp.getint(moduleName, self.ORDER_OPTION) + if order < len(cp.sections()): + for m in cp.sections(): + this_order = cp.getint(m, self.ORDER_OPTION) + if this_order == order + 1: + cp.set(m, self.ORDER_OPTION, str(order)) + cp.set(moduleName, self.ORDER_OPTION, str(order + 1)) + + # --------- + + def WriteAll(self): + for (name, cp) in self.collectionsConfig.items(): + self.WriteOne(name, cp) + + def WriteOne(self, name, cp): + #print "Writing:", name, cp.sections() + f = open(os.path.join(COLLECTIONS_PATH, + name + self.COLLECTIONS_SUFFIX), "w") + cp.write(f) + f.close() diff --git a/flextoolslib/code/FTConfig.py b/flextoolslib/code/FTConfig.py new file mode 100644 index 0000000..1a88054 --- /dev/null +++ b/flextoolslib/code/FTConfig.py @@ -0,0 +1,43 @@ +# +# Project: FlexTools +# Module: FTConfig +# +# Global definitions of data paths and configuration values. +# +# Craig Farrow +# Copyright 2012-2022 +# + +from os import getcwd +from os.path import dirname, join +from sys import argv + +from cdfutils.Config import ConfigStore + +import logging +logger = logging.getLogger(__name__) + +#----------------------------------------------------------- +# We're running from the folder where flextools.ini lives: +BASE_PATH = getcwd() +logger.info(f"Current working directory: {BASE_PATH}") + +# The default paths are relative to this directory +CONFIG_PATH = join(BASE_PATH, "flextools.ini") +MODULES_PATH = join(BASE_PATH, "Modules") +COLLECTIONS_PATH = join(BASE_PATH, "Collections") + +#----------------------------------------------------------- +# Load the configuration + +logger.debug(f"Reading configuration from {CONFIG_PATH}") +FTConfig = ConfigStore(CONFIG_PATH) + +# These are included in the configuration file in case users want +# to use a custom location. FlexTrans does this. +if not FTConfig.ModulesPath: + FTConfig.ModulesPath = MODULES_PATH + +if not FTConfig.CollectionsPath: + FTConfig.CollectionsPath = COLLECTIONS_PATH + diff --git a/FlexTools/FTModuleClass.py b/flextoolslib/code/FTModuleClass.py similarity index 95% rename from FlexTools/FTModuleClass.py rename to flextoolslib/code/FTModuleClass.py index 2b940fd..fb861c1 100644 --- a/FlexTools/FTModuleClass.py +++ b/flextoolslib/code/FTModuleClass.py @@ -1,124 +1,124 @@ -# -# Project: FlexTools -# Module: FTModuleClass -# -# FlexToolsModuleClass: The class used for an installable module. -# See the class definition for documentation. -# -# Craig Farrow -# October 2010 -# - -# FlexTools module documentation keys. These keys must be defined in the -# docs dictionary passed to the FlexToolsModuleClass initialisation. -FTM_Name = 'moduleName' -FTM_Version = 'moduleVersion' -FTM_ModifiesDB = 'moduleModifiesDB' -FTM_Synopsis = 'moduleSynopsis' -FTM_Help = 'moduleHelp' -FTM_Description = 'moduleDescription' - -# Private - don't define these in the module -FTM_Path = 'modulePath' -FTM_HasConfig = 'moduleHasConfiguration' # Note: configuration not implemented yet. - - -class FTM_ModuleError(Exception): - """Exception raised for any detectable errors in a module. - - Attributes: - message -- explanation of the error - """ - - def __init__(self, message): - self.message = message - - -class FlexToolsModuleClass (object): - """ - A FlexTools module (located in the Modules directory) should define: - FlexToolsModule = FlexToolsModuleClass(_processing_function_, - _user_documentation_) - - - _processing_function_ is a function of the form: - def _processing_function_(project, report, modifyAllowed): - - - _project_ is a FLExProject instance: - See flexlibs\FLExProject.py for the methods that are - provided. - - _report_ is a FTReport.FTReporter instance that reports - status messages: - report.Info(msg) - report.Warning(msg, reference) - report.Error(msg, reference) - - reference is an optional hyperlink to a lexical - entry in FLEx. - It is built with project.BuildGotoURL(entry) - - _modififyAllowed_ is True if the user has permitted any kind - of modification to the project. If this is False then the module - should ensure that no data is modified. - Users are warned to back up their projects before attempting - any modifications via a FlexTools module. - - - _user_documentation_ is a dictionary with the following keys defined: - FTM_Name : A short name for the module. - FTM_Version : The module version as a string or anything that - can be converted with str(). - e.g. an integer, "1.3", "Beta release 5", etc. - FTM_ModifiesDB : True/False indicating whether this module - makes changes to the project (when the user - permits it.) - FTM_Synopsis : A description of the module's function or purpose. - FTM_Help : A link to a help file for this module. - FTM_Description : A multi-line description of the module's - function. Please explain the behaviour and - purpose clearly. - If relevant, include specific information about - how the project is modified. - Exceptions: - KeyError - raised if there are missing documentation keys. - """ - __requiredDocKeys = [FTM_Name, - FTM_Version, - FTM_ModifiesDB, - FTM_Synopsis, - FTM_Description] - - def __init__(self, runFunction, docs, configuration=[]): - if any([x not in docs for x in self.__requiredDocKeys]): - raise FTM_ModuleError("Module documentation is missing required key.\n"\ - "Required keys:\n\t" + "\n\t".join(self.__requiredDocKeys)) - - self.docs = docs - self.configurationItems = configuration - self.runFunction = runFunction - - self.docs[FTM_HasConfig] = (len(self.configurationItems) > 0) - - def GetDocs(self): - return self.docs - - def GetConfigurables(self): - return self.configurationItems - - def Run(self, project, report, modifyAllowed = False): - if self.runFunction: - # Prevent writes if not documented - if modifyAllowed and not self.docs[FTM_ModifiesDB]: - report.Warning("Changes are enabled, but this module doesn't allow it. Disabling changes.") - modifyAllowed = False - self.runFunction(project, report, modifyAllowed) - - def Help(self): - # - # Returns information on this module formatted as a multi-line - # string. - # - - result = [] - for item in list(self.docs.items()): - result.append("%s : %s" % item) - - #if self.docs[FTM_HasConfig]:... - - return "\n".join(result) +# +# Project: FlexTools +# Module: FTModuleClass +# +# FlexToolsModuleClass: The class used for an installable module. +# See the class definition for documentation. +# +# Craig Farrow +# October 2010 +# + +# FlexTools module documentation keys. These keys must be defined in the +# docs dictionary passed to the FlexToolsModuleClass initialisation. +FTM_Name = 'moduleName' +FTM_Version = 'moduleVersion' +FTM_ModifiesDB = 'moduleModifiesDB' +FTM_Synopsis = 'moduleSynopsis' +FTM_Help = 'moduleHelp' +FTM_Description = 'moduleDescription' + +# Private - don't define these in the module +FTM_Path = 'modulePath' +FTM_HasConfig = 'moduleHasConfiguration' # Note: configuration not implemented yet. + + +class FTM_ModuleError(Exception): + """Exception raised for any detectable errors in a module. + + Attributes: + message -- explanation of the error + """ + + def __init__(self, message): + self.message = message + + +class FlexToolsModuleClass (object): + """ + A FlexTools module (located in the Modules directory) should define: + FlexToolsModule = FlexToolsModuleClass(_processing_function_, + _user_documentation_) + + - _processing_function_ is a function of the form: + def _processing_function_(project, report, modifyAllowed): + + - _project_ is a FLExProject instance: + See flexlibs\FLExProject.py for the methods that are + provided. + - _report_ is a FTReport.FTReporter instance that reports + status messages: + report.Info(msg) + report.Warning(msg, reference) + report.Error(msg, reference) + - reference is an optional hyperlink to a lexical + entry in FLEx. + It is built with project.BuildGotoURL(entry) + - _modififyAllowed_ is True if the user has permitted any kind + of modification to the project. If this is False then the module + should ensure that no data is modified. + Users are warned to back up their projects before attempting + any modifications via a FlexTools module. + + - _user_documentation_ is a dictionary with the following keys defined: + FTM_Name : A short name for the module. + FTM_Version : The module version as a string or anything that + can be converted with str(). + e.g. an integer, "1.3", "Beta release 5", etc. + FTM_ModifiesDB : True/False indicating whether this module + makes changes to the project (when the user + permits it.) + FTM_Synopsis : A description of the module's function or purpose. + FTM_Help : A link to a help file for this module. + FTM_Description : A multi-line description of the module's + function. Please explain the behaviour and + purpose clearly. + If relevant, include specific information about + how the project is modified. + Exceptions: + KeyError - raised if there are missing documentation keys. + """ + __requiredDocKeys = [FTM_Name, + FTM_Version, + FTM_ModifiesDB, + FTM_Synopsis, + FTM_Description] + + def __init__(self, runFunction, docs, configuration=[]): + if any([x not in docs for x in self.__requiredDocKeys]): + raise FTM_ModuleError("Module documentation is missing required key.\n"\ + "Required keys:\n\t" + "\n\t".join(self.__requiredDocKeys)) + + self.docs = docs + self.configurationItems = configuration + self.runFunction = runFunction + + self.docs[FTM_HasConfig] = (len(self.configurationItems) > 0) + + def GetDocs(self): + return self.docs + + def GetConfigurables(self): + return self.configurationItems + + def Run(self, project, report, modifyAllowed = False): + if self.runFunction: + # Prevent writes if not documented + if modifyAllowed and not self.docs[FTM_ModifiesDB]: + report.Info("(Modifications are allowed, but this module doesn't modify the project.)") + modifyAllowed = False + self.runFunction(project, report, modifyAllowed) + + def Help(self): + # + # Returns information on this module formatted as a multi-line + # string. + # + + result = [] + for item in list(self.docs.items()): + result.append("%s : %s" % item) + + #if self.docs[FTM_HasConfig]:... + + return "\n".join(result) diff --git a/FlexTools/FTModules.py b/flextoolslib/code/FTModules.py similarity index 96% rename from FlexTools/FTModules.py rename to flextoolslib/code/FTModules.py index 4018323..3969bbe 100644 --- a/FlexTools/FTModules.py +++ b/flextoolslib/code/FTModules.py @@ -1,242 +1,245 @@ -# -# Project: FlexTools -# Module: FTModules -# -# Class that loads and wraps FlexTools Modules. -# -# Craig Farrow -# Apr 2009 -# -# Attempts to load all *.py files in the Modules directory (including -# subdirectories). These all need to conform to the specification for -# FlexTools modules -- See FTModuleClass.py -# -# - -from builtins import str - -import os -import sys -import imp -import traceback - -import Version -import System - -import FTReport -from flexlibs import ( - FLExProject, - FP_ProjectError, - FP_RuntimeError, - ) - -from FTModuleClass import * - -# Loads .pth files from Modules\ -from FTPaths import MODULES_PATH -import site -site.addsitedir(MODULES_PATH) - - -import logging -logger = logging.getLogger(__name__) - -# ------------------------------------------------------------------ - -class ModuleManager (object): - - def __always_import(self, path, name): - # This code is from Python 2.5 Help section 29.1.1, but skipping - # the check for the module already being in sys.modules. - # This allows us to have more than one .py Module file with the same - # name, but in different directories. - - fp, pathname, description = imp.find_module(name, [path]) - - try: - return imp.load_module(name, fp, pathname, description) - except FTM_ModuleError as e: - msg = "%s\\%s:\n%s" % (path, name, e.message) - self.__errors.append(msg) - return None - except: - msg = "Module error: %s\n %s" % (pathname, traceback.format_exc()) - self.__errors.append(msg) - return None - finally: - # Since we may exit via an exception, close fp explicitly. - if fp: - fp.close() - - def __openProject(self, projectName, modifyAllowed): - #logger.debug("__openProject %s" % projectName) - self.project = FLExProject() - - try: - self.project.OpenProject(projectName, - writeEnabled = modifyAllowed) - except: - logger.error("Project failed to open: %s" % projectName) - del self.project - raise - logger.info("Project opened: %s" % projectName) - - def __closeProject(self): - #logger.debug("__closeProject %s" % repr(self.project)) - if self.project: - # Save any changes and release the LCM Cache. - self.project.CloseProject() - del self.project - - def __buildExceptionMessages(self, e, msg): - __copyMessage = "Use Ctrl-C to copy this report to the clipboard to see more information." - eName = details = "" - # Test for .NET Exception first, since they are also Python Exceptions. - if isinstance(e, System.Exception): #.NET - eName = e.GetType().FullName - details = e.ToString() # The full stack trace - elif isinstance(e, Exception): #Python - eName = sys.exc_info()[0].__name__ - eMsg = e.message if hasattr(e, "message") else "" - eStack = traceback.format_exc() - details = "{}: {}\n{}".format(eName, eMsg, eStack) - - return " ".join((msg.format(eName), __copyMessage)),\ - details - - # --- Public methods --- - - def LoadAll(self): - self.project = None - self.__modules = {} - self.__errors = [] - - libNames = [l for l in os.listdir(MODULES_PATH) \ - if os.path.isdir(os.path.join(MODULES_PATH,l))] \ - + [""] - - for library in libNames: - libPath = os.path.join(MODULES_PATH, library) - - modNames = [m[:-3] for m in os.listdir(libPath) - if m.endswith(".py")] - logger.info("From library %s: %s" % (library, repr(modNames))) - - for moduleFileName in modNames: - # Don't try to directly import python files starting with - # double underscore. - if moduleFileName.startswith("__"): - continue - - # Import named Python module from libPath - module = self.__always_import(libPath, moduleFileName) - - if not module: - logger.warning("Warning: FlexToolsModule import failure %s." % moduleFileName) - continue - try: - ftm = module.FlexToolsModule - except AttributeError: - logger.warning("Warning: FlexToolsModule not found in %s." % moduleFileName) - continue - - if library: - moduleFullName = ".".join([library, ftm.GetDocs()[FTM_Name]]) - modulePath = os.path.join(MODULES_PATH, library, moduleFileName) - else: - moduleFullName = ftm.GetDocs()[FTM_Name] - modulePath = os.path.join(MODULES_PATH, moduleFileName) - - if moduleFullName in self.__modules: - otherModule = self.__modules[moduleFullName].docs[FTM_Path] - errString = "Duplicate module names found in these files (using the first one):\n"\ - "\t%s \n\t%s" % (otherModule, modulePath) - self.__errors.append(errString) - continue - - ftm.docs[FTM_Path] = modulePath - self.__modules[moduleFullName] = ftm - - return self.__errors - - - def ListOfNames(self): - return sorted(self.__modules.keys()) - - def GetDocs(self, module_name): - try: - return self.__modules[module_name].GetDocs() - except KeyError: - return None - - def GetConfigurables(self, module_name): - try: - return self.__modules[module_name].GetConfigurables() - except KeyError: - return None - - def RunModules(self, projectName, moduleList, reporter, modifyAllowed = False): - if not projectName: - return False - - reporter.Info("Opening project %s..." % projectName) - try: - self.__openProject(projectName, modifyAllowed) - except FP_ProjectError as e: - reporter.Error("Error opening project: %s" - % e.message, e.message) - return False - except Exception as e: - msg, details = self.__buildExceptionMessages(e, "OpenProject failed with exception {}!") - reporter.Error(msg, details) - return False - - for moduleName in moduleList: - if not self.GetDocs(moduleName): - reporter.Warning("Module %s missing or failed to import." % moduleName) - continue - - reporter.Blank() - # Issue #20 - only display the base name of the module - # in the main UI. - displayName = moduleName.split(".", 1)[1] - reporter.Info("Running %s (version %s)..." % - (displayName, - str(self.__modules[moduleName].docs[FTM_Version]))) - - try: - self.__modules[moduleName].Run(self.project, - reporter, - modifyAllowed=modifyAllowed) - except FP_RuntimeError as e: - msg, details = self.__buildExceptionMessages(e, "Module failed with a programming error!") - reporter.Error(msg, details) - except Exception as e: - msg, details = self.__buildExceptionMessages(e, "Module failed with exception {}!") - reporter.Error(msg, details) - - numErrors = reporter.messageCounts[reporter.ERROR] - numWarnings = reporter.messageCounts[reporter.WARNING] - reporter.Info("Processing completed with %d error%s and %d warning%s" \ - % (numErrors, "" if numErrors==1 else "s", - numWarnings, "" if numWarnings==1 else "s")) - self.__closeProject() - - return True - - -# ------------------------------------------------------------------ - -if __name__ == '__main__': - mm = ModuleManager() - errList = mm.LoadAll() - if errList: - print(">>>> Errors <<<<") - print("\n".join(errList)) - - names = mm.ListOfNames() - for n in names: - print(">>>> %s <<<<" % n) - print(mm.GetDocs(n)) - print(mm.GetConfigurables(n)) - print() +# +# Project: FlexTools +# Module: FTModules +# +# Class that loads and wraps FlexTools Modules. +# +# Craig Farrow +# Apr 2009 +# +# Attempts to load all *.py files in the Modules directory (including +# subdirectories). These all need to conform to the specification for +# FlexTools modules -- See FTModuleClass.py +# +# + +from builtins import str + +import os +import sys +import imp +import traceback + +import System + +from . import FTReport +from flexlibs import ( + FLExProject, + FP_ProjectError, + FP_RuntimeError, + ) + +from .FTModuleClass import * + +# Loads .pth files from Modules\ +from .FTConfig import FTConfig +MODULES_PATH = FTConfig.ModulesPath + +import site +site.addsitedir(MODULES_PATH) + + +import logging +logger = logging.getLogger(__name__) + +# ------------------------------------------------------------------ + +class ModuleManager (object): + + def __always_import(self, path, name): + # This code is from Python 2.5 Help section 29.1.1, but skipping + # the check for the module already being in sys.modules. + # This allows us to have more than one .py Module file with the same + # name, but in different directories. + + fp, pathname, description = imp.find_module(name, [path]) + + try: + return imp.load_module(name, fp, pathname, description) + except FTM_ModuleError as e: + msg = "%s\\%s:\n%s" % (path, name, e.message) + self.__errors.append(msg) + return None + except: + msg = "Module error: %s\n %s" % (pathname, traceback.format_exc()) + self.__errors.append(msg) + return None + finally: + # Since we may exit via an exception, close fp explicitly. + if fp: + fp.close() + + def __openProject(self, projectName, modifyAllowed): + #logger.debug("__openProject %s" % projectName) + self.project = FLExProject() + + try: + self.project.OpenProject(projectName, + writeEnabled = modifyAllowed) + except: + logger.error("Project failed to open: %s" % projectName) + del self.project + raise + logger.info("Project opened: %s" % projectName) + + def __closeProject(self): + #logger.debug("__closeProject %s" % repr(self.project)) + if self.project: + # Save any changes and release the LCM Cache. + self.project.CloseProject() + del self.project + + def __buildExceptionMessages(self, e, msg): + __copyMessage = "Use Ctrl-C to copy this report to the clipboard to see more information." + eName = details = "" + # Test for .NET Exception first, since they are also Python Exceptions. + if isinstance(e, System.Exception): #.NET + eName = e.GetType().FullName + details = e.ToString() # The full stack trace + elif isinstance(e, Exception): #Python + eName = sys.exc_info()[0].__name__ + eMsg = e.message if hasattr(e, "message") else "" + eStack = traceback.format_exc() + details = "{}: {}\n{}".format(eName, eMsg, eStack) + + return " ".join((msg.format(eName), __copyMessage)),\ + details + + # --- Public methods --- + + def LoadAll(self): + self.project = None + self.__modules = {} + self.__errors = [] + + libNames = [l for l in os.listdir(MODULES_PATH) \ + if os.path.isdir(os.path.join(MODULES_PATH,l))] \ + + [""] + + for library in libNames: + libPath = os.path.join(MODULES_PATH, library) + + modNames = [m[:-3] for m in os.listdir(libPath) + if m.endswith(".py")] + logger.info("From library %s: %s" % (library, repr(modNames))) + + for moduleFileName in modNames: + # Don't try to directly import python files starting with + # double underscore. + if moduleFileName.startswith("__"): + continue + + # Import named Python module from libPath + module = self.__always_import(libPath, moduleFileName) + + if not module: + logger.warning("Warning: FlexToolsModule import failure %s." % moduleFileName) + continue + try: + ftm = module.FlexToolsModule + except AttributeError: + logger.warning("Warning: FlexToolsModule not found in %s." % moduleFileName) + continue + + if library: + moduleFullName = ".".join([library, ftm.GetDocs()[FTM_Name]]) + modulePath = os.path.join(MODULES_PATH, library, moduleFileName) + else: + moduleFullName = ftm.GetDocs()[FTM_Name] + modulePath = os.path.join(MODULES_PATH, moduleFileName) + + if moduleFullName in self.__modules: + otherModule = self.__modules[moduleFullName].docs[FTM_Path] + errString = "Duplicate module names found in these files (using the first one):\n"\ + "\t%s \n\t%s" % (otherModule, modulePath) + self.__errors.append(errString) + continue + + ftm.docs[FTM_Path] = modulePath + self.__modules[moduleFullName] = ftm + + return self.__errors + + + def ListOfNames(self): + return sorted(self.__modules.keys()) + + def GetDocs(self, module_name): + try: + return self.__modules[module_name].GetDocs() + except KeyError: + return None + + def GetConfigurables(self, module_name): + try: + return self.__modules[module_name].GetConfigurables() + except KeyError: + return None + + def RunModules(self, projectName, moduleList, reporter, modifyAllowed = False): + if not projectName: + return False + + reporter.Info("Opening project %s..." % projectName) + try: + self.__openProject(projectName, modifyAllowed) + except FP_ProjectError as e: + logger.error(e.message) + reporter.Error("Error opening project: %s" + % e.message, e.message) + return False + except Exception as e: + msg, details = self.__buildExceptionMessages(e, "OpenProject failed with exception {}!") + logger.error(details) + reporter.Error(msg, details) + return False + + for moduleName in moduleList: + if not self.GetDocs(moduleName): + reporter.Warning("Module %s missing or failed to import." % moduleName) + continue + + reporter.Blank() + # Issue #20 - only display the base name of the module + # in the main UI. + displayName = moduleName.split(".", 1)[1] + reporter.Info("Running %s (version %s)..." % + (displayName, + str(self.__modules[moduleName].docs[FTM_Version]))) + + try: + self.__modules[moduleName].Run(self.project, + reporter, + modifyAllowed=modifyAllowed) + except FP_RuntimeError as e: + msg, details = self.__buildExceptionMessages(e, "Module failed with a programming error!") + reporter.Error(msg, details) + except Exception as e: + msg, details = self.__buildExceptionMessages(e, "Module failed with exception {}!") + reporter.Error(msg, details) + + numErrors = reporter.messageCounts[reporter.ERROR] + numWarnings = reporter.messageCounts[reporter.WARNING] + reporter.Info("Processing completed with %d error%s and %d warning%s" \ + % (numErrors, "" if numErrors==1 else "s", + numWarnings, "" if numWarnings==1 else "s")) + self.__closeProject() + + return True + + +# ------------------------------------------------------------------ + +if __name__ == '__main__': + mm = ModuleManager() + errList = mm.LoadAll() + if errList: + print(">>>> Errors <<<<") + print("\n".join(errList)) + + names = mm.ListOfNames() + for n in names: + print(">>>> %s <<<<" % n) + print(mm.GetDocs(n)) + print(mm.GetConfigurables(n)) + print() diff --git a/FlexTools/FTReport.py b/flextoolslib/code/FTReport.py similarity index 96% rename from FlexTools/FTReport.py rename to flextoolslib/code/FTReport.py index 395f161..e152e9d 100644 --- a/FlexTools/FTReport.py +++ b/flextoolslib/code/FTReport.py @@ -1,94 +1,94 @@ -# -# Project: FlexTools -# Module: FTReport -# -# Report collater for FlexTools Modules: -# - A new instance is passed to each Module's Run() method. -# - The Module reports: -# - Blank: A blank line with no icon -# - Information: general status information -# - Warning: a warning message, with optional FLEx reference -# - Error: an error message, with optional FLEx reference -# - The UI displays this report information to the user. -# -# Craig Farrow -# Oct 2008 -# v0.00 -# - -# ------------------------------------------------------------------ - -class FTReporter(object): - INFO = 0 - WARNING = 1 - ERROR = 2 - BLANK = 3 - - def __init__(self): - self.__handler = None - self.__progressHandler = None - self.Reset() - - def RegisterProgressHandler(self, handler): - if handler: - self.__progressHandler = handler - self.progressMax = 0 - - def RegisterUIHandler(self, handler): - if handler: - self.__handler = handler - - def Reset(self): - self.messageCounts = [0,0,0,0] - self.messages = [] - - def __Report(self, msgType, msg, ref): - self.messages.append((msgType, msg, ref)) - self.messageCounts[msgType] += 1 - if self.__handler: - self.__handler(self.messages[-1]) - - # --- Public methods for FTModules to use - - # > Messages - - def Blank(self): - self.__Report(self.BLANK, None, None) - - def Info(self, msg, ref=None): - self.__Report(self.INFO, msg, ref) - - def Warning(self, msg, ref=None): - self.__Report(self.WARNING, msg, ref) - - def Error(self, msg, ref=None): - self.__Report(self.ERROR, msg, ref) - - # > Progress Bar - - def ProgressUpdate(self, value): - if self.__progressHandler: - self.__progressHandler(value+1, - self.progressMax, - self.progressMessage) - - def ProgressStart(self, max, msg=None): - self.progressMax = max - self.progressMessage = msg - self.ProgressUpdate(-1) - - def ProgressStop(self): - self.progressMax = 0 # Stop signal - self.progressMessage = "" - self.ProgressUpdate(-1) - - -if __name__ == '__main__': - f = FTReporter() - f.Info("Hi") - f.Blank() - f.Warning("Cows crossing") - f.Error("Bad bad news!") - print(f.messageCounts) - print(f.messages) - +# +# Project: FlexTools +# Module: FTReport +# +# Report collater for FlexTools Modules: +# - A new instance is passed to each Module's Run() method. +# - The Module reports: +# - Blank: A blank line with no icon +# - Information: general status information +# - Warning: a warning message, with optional FLEx reference +# - Error: an error message, with optional FLEx reference +# - The UI displays this report information to the user. +# +# Craig Farrow +# Oct 2008 +# v0.00 +# + +# ------------------------------------------------------------------ + +class FTReporter(object): + INFO = 0 + WARNING = 1 + ERROR = 2 + BLANK = 3 + + def __init__(self): + self.__handler = None + self.__progressHandler = None + self.Reset() + + def RegisterProgressHandler(self, handler): + if handler: + self.__progressHandler = handler + self.progressMax = 0 + + def RegisterUIHandler(self, handler): + if handler: + self.__handler = handler + + def Reset(self): + self.messageCounts = [0,0,0,0] + self.messages = [] + + def __Report(self, msgType, msg, ref): + self.messages.append((msgType, msg, ref)) + self.messageCounts[msgType] += 1 + if self.__handler: + self.__handler(self.messages[-1]) + + # --- Public methods for FTModules to use + + # > Messages + + def Blank(self): + self.__Report(self.BLANK, None, None) + + def Info(self, msg, ref=None): + self.__Report(self.INFO, msg, ref) + + def Warning(self, msg, ref=None): + self.__Report(self.WARNING, msg, ref) + + def Error(self, msg, ref=None): + self.__Report(self.ERROR, msg, ref) + + # > Progress Bar + + def ProgressUpdate(self, value): + if self.__progressHandler: + self.__progressHandler(value+1, + self.progressMax, + self.progressMessage) + + def ProgressStart(self, max, msg=None): + self.progressMax = max + self.progressMessage = msg + self.ProgressUpdate(-1) + + def ProgressStop(self): + self.progressMax = 0 # Stop signal + self.progressMessage = "" + self.ProgressUpdate(-1) + + +if __name__ == '__main__': + f = FTReporter() + f.Info("Hi") + f.Blank() + f.Warning("Cows crossing") + f.Error("Bad bad news!") + print(f.messageCounts) + print(f.messages) + diff --git a/FlexTools/Help.py b/flextoolslib/code/Help.py similarity index 96% rename from FlexTools/Help.py rename to flextoolslib/code/Help.py index 26636cd..ce68fe5 100644 --- a/FlexTools/Help.py +++ b/flextoolslib/code/Help.py @@ -9,15 +9,15 @@ # Nov 2010 # -import UIGlobal -import Version +from . import UIGlobal +from .. import version import os import sys from flexlibs import FWShortVersion, FWCodeDir, APIHelpFile -from FTModules import ModuleManager +from .FTModules import ModuleManager from System.Drawing import (Color, SystemColors, Point, PointF, Rectangle, Size, Bitmap, Image, Icon, @@ -52,7 +52,7 @@ def __init__(self): self.SelectionColor = Color.DarkSlateBlue self.SelectionFont = UIGlobal.smallFont - self.AppendText("%s\n\n" % Version.number) + self.AppendText(f"({version})\n\n") # The flextoolslib version self.SelectionColor = Color.DarkSlateBlue self.SelectionFont = UIGlobal.normalFont diff --git a/FlexTools/UICollections.py b/flextoolslib/code/UICollections.py similarity index 99% rename from FlexTools/UICollections.py rename to flextoolslib/code/UICollections.py index 87820bd..766d271 100644 --- a/FlexTools/UICollections.py +++ b/flextoolslib/code/UICollections.py @@ -19,10 +19,10 @@ # Oct 2009 # -import UIGlobal -from UIModuleBrowser import ModuleBrowser -import FTCollections -import FTModules +from . import UIGlobal +from .UIModuleBrowser import ModuleBrowser +from . import FTCollections +from . import FTModules from System.Drawing import (Color, SystemColors, Point, Rectangle, Size, Bitmap, diff --git a/FlexTools/UIGlobal.py b/flextoolslib/code/UIGlobal.py similarity index 89% rename from FlexTools/UIGlobal.py rename to flextoolslib/code/UIGlobal.py index 50aad0d..59c5350 100644 --- a/FlexTools/UIGlobal.py +++ b/flextoolslib/code/UIGlobal.py @@ -1,46 +1,47 @@ -# -# Project: FlexTools -# Module: UIGlobal -# -# Global settings, etc., for the FLExTools UI -# -# Craig Farrow -# Oct 2009-2019 -# - -import clr -clr.AddReference("System") -clr.AddReference("System.Drawing") -clr.AddReference("System.Windows.Forms") - -import os -import System -from System.Drawing import (Color, - Font, FontStyle, FontFamily) - -# Icon path details - -ICON_PATH0 = os.path.join(os.path.dirname(__file__), "__icons\\") -ICON_PATH1 = os.path.join(ICON_PATH0, "IconBuffet Redmond Studio\\") -ICON_SUFFIX1 = "_16.gif" -ICON_PATH2 = os.path.join(ICON_PATH0, "Fugue\\") -ICON_SUFFIX2 = ".png" - -ApplicationIcon = ICON_PATH0 + "flextools.ico" -ModuleIcon = ICON_PATH0 + "module.ico" -ToolbarIconParams = (ICON_PATH1, ICON_SUFFIX1) -ReportIconParams = (ICON_PATH2, ICON_SUFFIX2) - -# General appearance - -SPLITTER_WIDTH = 4 - -# Font styles -smallFont = Font(FontFamily.GenericSansSerif, 8.0) -normalFont = Font(FontFamily.GenericSansSerif, 10.0) -headingFont = Font(FontFamily.GenericSansSerif, 11.0) - -# Colours -leftPanelColor = Color.FromArgb(0xFA, 0xF8, 0xF0) # Light wheat -rightPanelColor = Color.FromArgb(0xFF, 0xFA, 0xE8) # Light cream -helpDialogColor = Color.White +# +# Project: FlexTools +# Module: UIGlobal +# +# Global settings, etc., for the FLExTools UI +# +# Craig Farrow +# Oct 2009-2022 +# + +import clr +clr.AddReference("System") +clr.AddReference("System.Drawing") +clr.AddReference("System.Windows.Forms") + +import os +import System +from System.Drawing import (Color, + Font, FontStyle, FontFamily) + +# Icon path details + +ICON_PATH0 = os.path.join(os.path.dirname(os.path.dirname(__file__)), + "icons\\") +ICON_PATH1 = os.path.join(ICON_PATH0, "IconBuffet Redmond Studio\\") +ICON_SUFFIX1 = "_16.gif" +ICON_PATH2 = os.path.join(ICON_PATH0, "Fugue\\") +ICON_SUFFIX2 = ".png" + +ApplicationIcon = ICON_PATH0 + "flextools.ico" +ModuleIcon = ICON_PATH0 + "module.ico" +ToolbarIconParams = (ICON_PATH1, ICON_SUFFIX1) +ReportIconParams = (ICON_PATH2, ICON_SUFFIX2) + +# General appearance + +SPLITTER_WIDTH = 4 + +# Font styles +smallFont = Font(FontFamily.GenericSansSerif, 8.0) +normalFont = Font(FontFamily.GenericSansSerif, 10.0) +headingFont = Font(FontFamily.GenericSansSerif, 11.0) + +# Colours +leftPanelColor = Color.FromArgb(0xFA, 0xF8, 0xF0) # Light wheat +rightPanelColor = Color.FromArgb(0xFF, 0xFA, 0xE8) # Light cream +helpDialogColor = Color.White diff --git a/FlexTools/FLExTools.py b/flextoolslib/code/UIMain.py similarity index 78% rename from FlexTools/FLExTools.py rename to flextoolslib/code/UIMain.py index 090c14e..874208b 100644 --- a/FlexTools/FLExTools.py +++ b/flextoolslib/code/UIMain.py @@ -1,46 +1,19 @@ # # Project: FlexTools -# Module: FLExTools +# Module: UIMain # Platform: .NET v2 Windows.Forms (Python.NET 2.7) # -# Main FLexTools UI: +# Main FlexTools UI: # - A split panel with Collections list above and Report results below. # # -# Copyright Craig Farrow, 2010 - 2019 +# Copyright Craig Farrow, 2010 - 2022 # -from __future__ import unicode_literals -from builtins import str -import codecs -import sys -sys.stdout = codecs.getwriter("utf-8")(sys.stdout) - -import os -import traceback - - -# ----------------------------------------------------------- import logging - -if len(sys.argv) > 1 and (sys.argv[1] == "DEBUG"): - loggingLevel = logging.DEBUG -else: - loggingLevel = logging.INFO - -logging.basicConfig(filename='flextools.log', - filemode='w', - level=loggingLevel) - logger = logging.getLogger(__name__) -# ----------------------------------------------------------- - -# This call is required to initialise the threading mode for COM calls -# (e.g. using the clipboard) It must be made before clr is imported. -import ctypes -ctypes.windll.ole32.CoInitialize(None) import clr import System @@ -72,32 +45,16 @@ from System.Threading import Thread, ThreadStart, ApartmentState -import FTPaths -import Version - -logger.info("FLExTools %s" % Version.number) - -try: - from flexlibs import FLExInitialize, FLExCleanup - -except Exception as e: - MessageBox.Show("There was an issue interfacing with FieldWorks:\n%s\n(This version of FLExTools has been tested with Fieldworks versions %s - %s.)\nSee flextools.log for more details." - % (e, Version.MinFWVersion, Version.MaxFWVersion), - "FLExTools: Fatal Error", - MessageBoxButtons.OK, - MessageBoxIcon.Exclamation) - logger.error("Fatal exception importing flexlibs:\n%s" % traceback.format_exc()) - sys.exit(1) - -import UIGlobal -import UICollections, FTCollections -import UIModulesList, UIReport, UIModuleBrowser -from UIProjectChooser import ProjectChooser -import FTModules -import Help +from .. import version +from . import UIGlobal +from .FTConfig import FTConfig +from . import UICollections, FTCollections +from . import UIModulesList, UIReport, UIModuleBrowser +from .UIProjectChooser import ProjectChooser +from . import FTModules +from . import Help from cdfutils.DotNet import CustomMainMenu, CustomToolBar -from cdfutils.Config import ConfigStore # ------------------------------------------------------------------ @@ -144,7 +101,11 @@ def __InitToolBar(self): return CustomToolBar(ButtonList, UIGlobal.ToolbarIconParams) - def __init__(self, moduleManager, projectName, listOfModules, reloadFunction, progressFunction): + def __init__(self, + moduleManager, + listOfModules, + reloadFunction, + progressFunction): Panel.__init__(self) self.Dock = DockStyle.Fill @@ -156,7 +117,6 @@ def __init__(self, moduleManager, projectName, listOfModules, reloadFunction, pr # -- Module list and Report window self.moduleManager = moduleManager - self.projectName = projectName self.listOfModules = listOfModules self.reloadFunction = reloadFunction @@ -169,8 +129,8 @@ def __init__(self, moduleManager, projectName, listOfModules, reloadFunction, pr self.startupToolTip = None startupTips = [] - if projectName: - self.UpdateProjectName(projectName) + if FTConfig.currentProject: + self.UpdateProjectName() else: msg = "Choose a project by clicking the Choose Project button in the toolbar." startupTips.append(msg) @@ -225,7 +185,7 @@ def __Run(self, message, modules, modifyAllowed = False): # Reload the modules to make sure we're using the latest code. if self.reloadFunction: self.reloadFunction() - if not self.projectName: + if not FTConfig.currentProject: self.reportWindow.Reporter.Error("No project selected! Use the Choose Project button in the toolbar.") return @@ -235,20 +195,21 @@ def __Run(self, message, modules, modifyAllowed = False): self.reportWindow.Clear() if modifyAllowed: - dlgmsg = "Are you sure you want to make changes to the '%s' project? "\ - "Please back up the project first." - title = "Confirm allow changes" - result = MessageBox.Show(dlgmsg % self.projectName, title, - MessageBoxButtons.YesNo, - MessageBoxIcon.Question) - if (result == DialogResult.No): - return + if (FTConfig.warnOnModify): + dlgmsg = "Are you sure you want to make changes to the '%s' project? "\ + "Please back up the project first." + title = "Allow changes?" + result = MessageBox.Show(dlgmsg % FTConfig.currentProject, title, + MessageBoxButtons.YesNo, + MessageBoxIcon.Question) + if (result == DialogResult.No): + return message += " (Changes enabled)" self.reportWindow.Reporter.Info(message) self.reportWindow.Refresh() - self.moduleManager.RunModules(self.projectName, + self.moduleManager.RunModules(FTConfig.currentProject, modules, self.reportWindow.Reporter, modifyAllowed) @@ -288,13 +249,12 @@ def UpdateModuleList(self, collectionName, listOfModules): self.modulesList.UpdateAllItems(self.listOfModules) self.reportWindow.Report("Collection '%s' selected." % collectionName) - def UpdateProjectName(self, newProjectName): + def UpdateProjectName(self): if self.startupToolTip: self.startupToolTip.RemoveAll() - self.projectName = newProjectName - self.reportWindow.Report("Project '%s' selected." % self.projectName) - self.toolbar.UpdateButtonText(0, self.projectName) - + self.reportWindow.Report("Project '%s' selected." % FTConfig.currentProject) + self.toolbar.UpdateButtonText(0, FTConfig.currentProject) + def ModuleInfo(self): if self.modulesList.SelectedIndex >= 0: module = self.listOfModules[self.modulesList.SelectedIndex] @@ -414,16 +374,16 @@ def __LoadModules(self): if hasattr(self, "UIPanel"): self.UIPanel.RefreshModules() - def __init__(self): + def __init__(self, appVersion): Form.__init__(self) self.ClientSize = Size(700, 500) - self.Text = "FLExTools " + Version.number + self.Text = "FLExTools " + appVersion - ## Get configurables - current project, current collection - logger.debug("Reading configuration") - self.configuration = ConfigStore(FTPaths.CONFIG_PATH) - if not self.configuration.currentCollection: - self.configuration.currentCollection = "Examples" + ## Initialise default configuration values + if not FTConfig.currentCollection: + FTConfig.currentCollection = "Examples" + if FTConfig.warnOnModify == None: + FTConfig.warnOnModify = True self.collectionsManager = FTCollections.CollectionsManager() @@ -431,10 +391,10 @@ def __init__(self): try: listOfModules = self.collectionsManager.ListOfModules( - self.configuration.currentCollection) + FTConfig.currentCollection) except FTCollections.FTC_NameError: # The configuration value is bad... - self.configuration.currentCollection = None + FTConfig.currentCollection = None listOfModules = [] self.Icon = Icon(UIGlobal.ApplicationIcon) @@ -447,10 +407,9 @@ def __init__(self): self.__UpdateStatusBar() self.UIPanel = FTPanel(self.moduleManager, - self.configuration.currentProject, listOfModules, self.__LoadModules, - self.__ProgressBar + self.__ProgressBar, ) self.UIPanel.SetChooseProjectHandler(self.ChooseProject) self.UIPanel.SetEditCollectionsHandler(self.EditCollections) @@ -461,7 +420,7 @@ def __init__(self): def __OnFormClosed(self, sender, event): # Event triggered when self.Close() is called. - del self.configuration # Save data by deleting ConfigStore object + # del FTConfig # Save data by deleting ConfigStore object pass # ---- @@ -472,9 +431,9 @@ def __UpdateStatusBar(self): else: progressText = "" newText = "Collection: '%s' Project: '%s' %s" %\ - (self.configuration.currentCollection, - #self.configuration.currentServer - self.configuration.currentProject, + (FTConfig.currentCollection, + #FTConfig.currentServer + FTConfig.currentProject, progressText) if self.StatusBar.Text != newText: self.StatusBar.Text = newText @@ -509,28 +468,28 @@ def EditCollections(self, sender=None, event=None): dlg = UICollections.CollectionsDialog(self.collectionsManager, self.moduleManager, - self.configuration.currentCollection) + FTConfig.currentCollection) dlg.ShowDialog() - self.configuration.currentCollection = dlg.activatedCollection + FTConfig.currentCollection = dlg.activatedCollection self.__UpdateStatusBar() # Always refresh the list in case the current one was changed. try: listOfModules = self.collectionsManager.ListOfModules( - self.configuration.currentCollection) + FTConfig.currentCollection) except FTCollections.FTC_NameError: # The configuration value is bad or None... - self.configuration.currentCollection = None + FTConfig.currentCollection = None listOfModules = [] - self.UIPanel.UpdateModuleList(self.configuration.currentCollection, + self.UIPanel.UpdateModuleList(FTConfig.currentCollection, listOfModules) def ChooseProject(self, sender=None, event=None): - dlg = ProjectChooser(self.configuration.currentProject) + dlg = ProjectChooser(FTConfig.currentProject) dlg.ShowDialog() - if dlg.projectName != self.configuration.currentProject: - self.configuration.currentProject = dlg.projectName - self.UIPanel.UpdateProjectName(dlg.projectName) + if dlg.projectName != FTConfig.currentProject: + FTConfig.currentProject = dlg.projectName + self.UIPanel.UpdateProjectName() self.__UpdateStatusBar() def CopyToClipboard(self, sender, event): @@ -554,15 +513,5 @@ def RunAll(self, sender, event): def RunAllModify(self, sender, event): self.UIPanel.RunAllModify() -# ------------------------------------------------------------------ -def main(): - FLExInitialize() - - logger.debug("Creating MainForm") - form = FTMainForm() - logger.debug("Launching WinForms Application") - Application.Run(form) - FLExCleanup() -main() diff --git a/FlexTools/UIModuleBrowser.py b/flextoolslib/code/UIModuleBrowser.py similarity index 96% rename from FlexTools/UIModuleBrowser.py rename to flextoolslib/code/UIModuleBrowser.py index 1f60658..63ea74d 100644 --- a/FlexTools/UIModuleBrowser.py +++ b/flextoolslib/code/UIModuleBrowser.py @@ -1,264 +1,264 @@ -# -# Project: FlexTools -# Module: UIModuleBrowser -# Platform: .NET v2 Windows.Forms -# -# UI for browsing and selecting Modules: -# - Split panel with Module tree (folders & nodes) on left -# and full information about the Module on right. -# -# TODO: -# -# Craig Farrow -# Oct 2009 -# v0.01 -# - -from __future__ import unicode_literals -from builtins import str - -import os - -import UIGlobal -from FTModuleClass import * - -from System.Drawing import (Color, Point, Rectangle, Size, Bitmap, - Icon, - Font, FontStyle, FontFamily) -from System.Windows.Forms import (Application, BorderStyle, Button, - Form, FormBorderStyle, Label, - Panel, Screen, - MessageBox, MessageBoxButtons, MessageBoxIcon, - DockStyle, Orientation, View, SortOrder, - TreeView, TreeViewAction, - ListView, ListViewItem, HorizontalAlignment, ImageList, - ToolTip, - KeyEventArgs, KeyPressEventArgs, Keys, - RichTextBox, HtmlDocument, SplitContainer ) - -class ModuleTree(TreeView): - def __init__(self): - TreeView.__init__(self) - - - self.Dock = DockStyle.Fill - self.ShowLines = False - self.TabIndex = 0 - #self.BorderStyle = BorderStyle.None - self.BackColor = UIGlobal.leftPanelColor - self.FullRowSelect = True - self.HideSelection = False # Keep selected item grey when lost focus - - tooltip = ToolTip() - tooltip.IsBalloon = True - tooltip.SetToolTip(self, "Double-click a module to add it to the collection.") - - def SetSelectedHandler(self, handler): - self.AfterSelect += handler - - def SetActivatedHandler(self, handler): - if handler: - self.__ActivatedHandler = handler - self.DoubleClick += self.__ItemActivatedHandler - self.KeyDown += self.__ItemActivatedHandler - - def __ItemActivatedHandler(self, sender, event): - if self.__ActivatedHandler: - activate = True # For DoubleClick - if type(event) == KeyEventArgs: - activate = (event.KeyCode == Keys.Return) - if activate: - if sender.SelectedNode.Nodes.Count == 0: - self.__ActivatedHandler(sender.SelectedNode.Name) - - - def AddModuleName(self, moduleName): - # The Key value is set to the full moduleName for looking up - # the module, whereas the text displayed in the node has - # the library name deleted. - - dotpos = moduleName.find(".") - if dotpos > 0: - libText = moduleName[:dotpos] - moduleText = moduleName[dotpos+1:] - - idx = self.Nodes.IndexOfKey(libText) - if idx >= 0: - self.Nodes[idx].Nodes.Add(moduleName, moduleText) - else: - - node = self.Nodes.Add(libText, libText) # Key & Label - node.NodeFont = Font(UIGlobal.normalFont,FontStyle.Italic) - node.ForeColor = Color.DarkSlateBlue - node.Nodes.Add(moduleName, moduleText) - else: - self.Nodes.Add(moduleName, moduleName) - - -class ModuleInfoPane(RichTextBox): - def __init__(self): - RichTextBox.__init__(self) - self.Dock = DockStyle.Fill - self.BackColor = UIGlobal.rightPanelColor - self.SelectionProtected = True - self.TabIndex = 1 - self.LinkClicked += self.__OnLinkClicked - - def SetFromDocs(self, moduleDocs): - self.Clear() - - # Module Name - self.SelectionFont = UIGlobal.headingFont - self.SelectionAlignment = HorizontalAlignment.Center - self.AppendText(moduleDocs[FTM_Name]+"\n") - - self.SelectionIndent = 8 - - self.SelectionAlignment = HorizontalAlignment.Left - - # Module Version - self.SelectionFont = UIGlobal.normalFont - self.SelectionColor = Color.Blue - self.AppendText("\nVersion: %s\n" % str(moduleDocs[FTM_Version])) - - # Modification warning - if moduleDocs[FTM_ModifiesDB]: - self.SelectionFont = UIGlobal.normalFont - self.SelectionColor = Color.Red - self.AppendText("Can modify the project\n") - - # Module Synopsis - self.SelectionColor = Color.DarkSlateBlue - self.SelectionFont = UIGlobal.normalFont - self.AppendText(moduleDocs[FTM_Synopsis]+"\n") - - # Module Help - if FTM_Help in moduleDocs and moduleDocs[FTM_Help]: - self.SelectionFont = UIGlobal.normalFont - self.AppendText("Help: ") - self.HelpLink = moduleDocs[FTM_Help] - - s = self.SelectionStart - link = "file:%s\n" % moduleDocs[FTM_Help] - # Change spaces to underscores so the whole path is treated as a hyperlink - self.AppendText(link.replace(" ", "_")) - # Build the actual hyperlink with full path - self.HelpLink = os.path.sep.join((os.path.split(moduleDocs[FTM_Path])[0], - moduleDocs[FTM_Help])) - - self.SelectionFont = UIGlobal.smallFont - self.AppendText("\n") - - # Module Description - self.SelectionColor = Color.Black - - start = self.SelectionStart - # convert all single newlines to spaces - md = moduleDocs[FTM_Description].replace("\n\n", "").split() - md = " ".join(md).replace("", "\n\n") - self.AppendText(md) - - # Chinese text messes up the font: setting the font after Append fixes it. - self.Select(start,self.TextLength) # Start, Length - self.SelectionFont = UIGlobal.normalFont - self.AppendText("\n") # NL, and puts insertion point at end. - - # Module filename - self.SelectionIndent = 0 - self.SelectionHangingIndent = 0 - self.SelectionRightIndent = 0 - self.SelectionColor = Color.Black - self.SelectionFont = UIGlobal.smallFont - self.AppendText("\nSource file: " + moduleDocs[FTM_Path] + ".py\n") - - # Make it non-editable - self.SelectAll() - self.SelectionProtected = True - self.Select(0,0) - - def __OnLinkClicked(self, sender, event): - try: - os.startfile(self.HelpLink) - except WindowsError as e: - MessageBox.Show("Error opening link: %s" % self.HelpLink, - "Error!" , - MessageBoxButtons.OK, - MessageBoxIcon.Error) - - -class ModuleBrowser(Panel): - def __init__(self, modules): - Panel.__init__(self) - self.modules = modules - self.Dock = DockStyle.Fill - - self.moduleActivatedHandler = None - - # The infoPane contents is initialised through AfterSelect event - # from ModuleTree - self.infoPane = ModuleInfoPane() - - self.moduleTree = ModuleTree() - for m in self.modules.ListOfNames(): - self.moduleTree.AddModuleName(m) - self.moduleTree.ExpandAll() - self.moduleTree.SetSelectedHandler(self.TreeNodeSelected) - - self.splitContainer1 = SplitContainer() - self.splitContainer1.Dock = DockStyle.Fill - self.splitContainer1.TabIndex = 1 - self.splitContainer1.SplitterWidth = UIGlobal.SPLITTER_WIDTH - self.splitContainer1.SplitterDistance = 70 - self.splitContainer1.Orientation = Orientation.Vertical - self.splitContainer1.Panel1.Controls.Add(self.moduleTree) - self.splitContainer1.Panel2.Controls.Add(self.infoPane) - - ## Add the main SplitContainer control to the panel. - self.Controls.Add(self.splitContainer1) - - def TreeNodeSelected(self, sender, event): - ##print "Got TreeNodeSelected" - if sender.SelectedNode.Nodes.Count != 0: - ##print "This is a Library node" - self.infoPane.Text = "Library: "+sender.SelectedNode.Text - self.selectedNode = "" - else: - # .Name == The Key given when adding to the TreeNodeCollection - #print "Node is:", sender.SelectedNode.Name - self.infoPane.SetFromDocs ( - self.modules.GetDocs(sender.SelectedNode.Name)) - self.selectedNode = sender.SelectedNode.Name - - def SetActivatedHandler(self, handler): - self.moduleTree.SetActivatedHandler(handler) - - - -# ------------------------------------------------------------------ - -class ModuleInfoDialog(Form): - def __init__(self, docs): - Form.__init__(self) - self.ClientSize = Size(400, 400) - self.Text = "Module Details" - self.Icon = Icon(UIGlobal.ApplicationIcon) - - infoPane = ModuleInfoPane() - infoPane.SetFromDocs(docs) - self.Controls.Add(infoPane) - -# ------------------------------------------------------------------ - -if __name__ == "__main__": - import FTModules - mm = FTModules.ModuleManager() - mm.LoadAll() - - mb = ModuleBrowser(mm) - - form = Form() - form.ClientSize = Size(500, 300) - - form.Text = "Test of Module Browser" - form.Controls.Add(mb) - Application.Run(form) +# +# Project: FlexTools +# Module: UIModuleBrowser +# Platform: .NET v2 Windows.Forms +# +# UI for browsing and selecting Modules: +# - Split panel with Module tree (folders & nodes) on left +# and full information about the Module on right. +# +# TODO: +# +# Craig Farrow +# Oct 2009 +# v0.01 +# + +from __future__ import unicode_literals +from builtins import str + +import os + +from . import UIGlobal +from .FTModuleClass import * + +from System.Drawing import (Color, Point, Rectangle, Size, Bitmap, + Icon, + Font, FontStyle, FontFamily) +from System.Windows.Forms import (Application, BorderStyle, Button, + Form, FormBorderStyle, Label, + Panel, Screen, + MessageBox, MessageBoxButtons, MessageBoxIcon, + DockStyle, Orientation, View, SortOrder, + TreeView, TreeViewAction, + ListView, ListViewItem, HorizontalAlignment, ImageList, + ToolTip, + KeyEventArgs, KeyPressEventArgs, Keys, + RichTextBox, HtmlDocument, SplitContainer ) + +class ModuleTree(TreeView): + def __init__(self): + TreeView.__init__(self) + + + self.Dock = DockStyle.Fill + self.ShowLines = False + self.TabIndex = 0 + #self.BorderStyle = BorderStyle.None + self.BackColor = UIGlobal.leftPanelColor + self.FullRowSelect = True + self.HideSelection = False # Keep selected item grey when lost focus + + tooltip = ToolTip() + tooltip.IsBalloon = True + tooltip.SetToolTip(self, "Double-click a module to add it to the collection.") + + def SetSelectedHandler(self, handler): + self.AfterSelect += handler + + def SetActivatedHandler(self, handler): + if handler: + self.__ActivatedHandler = handler + self.DoubleClick += self.__ItemActivatedHandler + self.KeyDown += self.__ItemActivatedHandler + + def __ItemActivatedHandler(self, sender, event): + if self.__ActivatedHandler: + activate = True # For DoubleClick + if type(event) == KeyEventArgs: + activate = (event.KeyCode == Keys.Return) + if activate: + if sender.SelectedNode.Nodes.Count == 0: + self.__ActivatedHandler(sender.SelectedNode.Name) + + + def AddModuleName(self, moduleName): + # The Key value is set to the full moduleName for looking up + # the module, whereas the text displayed in the node has + # the library name deleted. + + dotpos = moduleName.find(".") + if dotpos > 0: + libText = moduleName[:dotpos] + moduleText = moduleName[dotpos+1:] + + idx = self.Nodes.IndexOfKey(libText) + if idx >= 0: + self.Nodes[idx].Nodes.Add(moduleName, moduleText) + else: + + node = self.Nodes.Add(libText, libText) # Key & Label + node.NodeFont = Font(UIGlobal.normalFont,FontStyle.Italic) + node.ForeColor = Color.DarkSlateBlue + node.Nodes.Add(moduleName, moduleText) + else: + self.Nodes.Add(moduleName, moduleName) + + +class ModuleInfoPane(RichTextBox): + def __init__(self): + RichTextBox.__init__(self) + self.Dock = DockStyle.Fill + self.BackColor = UIGlobal.rightPanelColor + self.SelectionProtected = True + self.TabIndex = 1 + self.LinkClicked += self.__OnLinkClicked + + def SetFromDocs(self, moduleDocs): + self.Clear() + + # Module Name + self.SelectionFont = UIGlobal.headingFont + self.SelectionAlignment = HorizontalAlignment.Center + self.AppendText(moduleDocs[FTM_Name]+"\n") + + self.SelectionIndent = 8 + + self.SelectionAlignment = HorizontalAlignment.Left + + # Module Version + self.SelectionFont = UIGlobal.normalFont + self.SelectionColor = Color.Blue + self.AppendText("\nVersion: %s\n" % str(moduleDocs[FTM_Version])) + + # Modification warning + if moduleDocs[FTM_ModifiesDB]: + self.SelectionFont = UIGlobal.normalFont + self.SelectionColor = Color.Red + self.AppendText("Can modify the project\n") + + # Module Synopsis + self.SelectionColor = Color.DarkSlateBlue + self.SelectionFont = UIGlobal.normalFont + self.AppendText(moduleDocs[FTM_Synopsis]+"\n") + + # Module Help + if FTM_Help in moduleDocs and moduleDocs[FTM_Help]: + self.SelectionFont = UIGlobal.normalFont + self.AppendText("Help: ") + self.HelpLink = moduleDocs[FTM_Help] + + s = self.SelectionStart + link = "file:%s\n" % moduleDocs[FTM_Help] + # Change spaces to underscores so the whole path is treated as a hyperlink + self.AppendText(link.replace(" ", "_")) + # Build the actual hyperlink with full path + self.HelpLink = os.path.sep.join((os.path.split(moduleDocs[FTM_Path])[0], + moduleDocs[FTM_Help])) + + self.SelectionFont = UIGlobal.smallFont + self.AppendText("\n") + + # Module Description + self.SelectionColor = Color.Black + + start = self.SelectionStart + # convert all single newlines to spaces + md = moduleDocs[FTM_Description].replace("\n\n", "").split() + md = " ".join(md).replace("", "\n\n") + self.AppendText(md) + + # Chinese text messes up the font: setting the font after Append fixes it. + self.Select(start,self.TextLength) # Start, Length + self.SelectionFont = UIGlobal.normalFont + self.AppendText("\n") # NL, and puts insertion point at end. + + # Module filename + self.SelectionIndent = 0 + self.SelectionHangingIndent = 0 + self.SelectionRightIndent = 0 + self.SelectionColor = Color.Black + self.SelectionFont = UIGlobal.smallFont + self.AppendText("\nSource file: " + moduleDocs[FTM_Path] + ".py\n") + + # Make it non-editable + self.SelectAll() + self.SelectionProtected = True + self.Select(0,0) + + def __OnLinkClicked(self, sender, event): + try: + os.startfile(self.HelpLink) + except WindowsError as e: + MessageBox.Show("Error opening link: %s" % self.HelpLink, + "Error!" , + MessageBoxButtons.OK, + MessageBoxIcon.Error) + + +class ModuleBrowser(Panel): + def __init__(self, modules): + Panel.__init__(self) + self.modules = modules + self.Dock = DockStyle.Fill + + self.moduleActivatedHandler = None + + # The infoPane contents is initialised through AfterSelect event + # from ModuleTree + self.infoPane = ModuleInfoPane() + + self.moduleTree = ModuleTree() + for m in self.modules.ListOfNames(): + self.moduleTree.AddModuleName(m) + self.moduleTree.ExpandAll() + self.moduleTree.SetSelectedHandler(self.TreeNodeSelected) + + self.splitContainer1 = SplitContainer() + self.splitContainer1.Dock = DockStyle.Fill + self.splitContainer1.TabIndex = 1 + self.splitContainer1.SplitterWidth = UIGlobal.SPLITTER_WIDTH + self.splitContainer1.SplitterDistance = 70 + self.splitContainer1.Orientation = Orientation.Vertical + self.splitContainer1.Panel1.Controls.Add(self.moduleTree) + self.splitContainer1.Panel2.Controls.Add(self.infoPane) + + ## Add the main SplitContainer control to the panel. + self.Controls.Add(self.splitContainer1) + + def TreeNodeSelected(self, sender, event): + ##print "Got TreeNodeSelected" + if sender.SelectedNode.Nodes.Count != 0: + ##print "This is a Library node" + self.infoPane.Text = "Library: "+sender.SelectedNode.Text + self.selectedNode = "" + else: + # .Name == The Key given when adding to the TreeNodeCollection + #print "Node is:", sender.SelectedNode.Name + self.infoPane.SetFromDocs ( + self.modules.GetDocs(sender.SelectedNode.Name)) + self.selectedNode = sender.SelectedNode.Name + + def SetActivatedHandler(self, handler): + self.moduleTree.SetActivatedHandler(handler) + + + +# ------------------------------------------------------------------ + +class ModuleInfoDialog(Form): + def __init__(self, docs): + Form.__init__(self) + self.ClientSize = Size(400, 400) + self.Text = "Module Details" + self.Icon = Icon(UIGlobal.ApplicationIcon) + + infoPane = ModuleInfoPane() + infoPane.SetFromDocs(docs) + self.Controls.Add(infoPane) + +# ------------------------------------------------------------------ + +if __name__ == "__main__": + import FTModules + mm = FTModules.ModuleManager() + mm.LoadAll() + + mb = ModuleBrowser(mm) + + form = Form() + form.ClientSize = Size(500, 300) + + form.Text = "Test of Module Browser" + form.Controls.Add(mb) + Application.Run(form) diff --git a/FlexTools/UIModulesList.py b/flextoolslib/code/UIModulesList.py similarity index 95% rename from FlexTools/UIModulesList.py rename to flextoolslib/code/UIModulesList.py index be3e65d..1efbcdc 100644 --- a/FlexTools/UIModulesList.py +++ b/flextoolslib/code/UIModulesList.py @@ -1,171 +1,170 @@ -# -# Project: FlexTools -# Module: UIModulesList -# Platform: .NET v2 Windows.Forms (Python.NET 2.5) -# -# A custom list for selecting the modules in the main FLexTools UI. -# -# -# Copyright Craig Farrow, 2010 - 2018 -# - -from __future__ import unicode_literals -from builtins import str - -import UIGlobal -import Version -from FTModuleClass import * - -from System.Drawing import (Color, SystemColors, Point, PointF, Rectangle, - Size, Bitmap, Image, Icon, SystemIcons, - SolidBrush, Pens, Font, FontStyle, FontFamily) - -from System.Windows.Forms import ( - ListBox, DrawMode, - Button, - DockStyle, Orientation, View, SortOrder, - HorizontalAlignment, ImageList, - DrawItemState, DrawItemEventArgs, - KeyEventArgs, KeyPressEventArgs, Keys, - TextRenderer) - -# ------------------------------------------------------------------ - - -class ModulesList(ListBox): - SPACING = 1 - ICON_SPACE = 34 - - def __init__(self, moduleManager, contents): - ListBox.__init__(self) - - self.moduleManager = moduleManager - - # behaviour - self.LabelEdit = False - self.MultiSelect = False - - # appearance - self.Dock = DockStyle.Fill - self.ScrollAlwaysVisible = True - self.TabIndex = 0 - self.HideSelection = False # Keep selected item grey when lost focus - - self.textBrush = SolidBrush(SystemColors.WindowText) - self.icon = Bitmap(UIGlobal.ModuleIcon) - - self.DrawMode = DrawMode.OwnerDrawVariable - self.DrawItem += self.OnDrawItem - self.MeasureItem += self.OnMeasureItem - - self.UpdateAllItems(contents) - - # ---- Custom drawing methods ---- - - def __countLines(self, s): - return len(s.split("\n")) - - def OnMeasureItem(self, sender, e): - #print "Measuring:", sender.Items[e.Index] - if e.Index < 0: return - textHeight = self.Font.Height * self.__countLines(sender.Items[e.Index]) - e.ItemHeight = textHeight + 2 + self.SPACING # extra for separator line - - def OnDrawItem(self, sender, e): - if e.Index < 0: return - - g = e.Graphics - - ## Custom draw... an icon and multi-line text - - # Use our own size to preserve the border line - myBounds = e.Bounds - myBounds.Height -= self.SPACING - - imageRect = myBounds.MemberwiseClone() - imageRect.Inflate(-1,-1) - imageRect.Width = self.icon.Width - imageRect.X += (self.ICON_SPACE - self.icon.Width) / 2 - imageRect.Y += (imageRect.Height - self.icon.Height) / 2 - imageRect.Height = self.icon.Height - - textRect = myBounds.MemberwiseClone() - textRect.X += self.ICON_SPACE - textRect.Width -= self.ICON_SPACE - textRect.Height -= 1 - textRect.Inflate(-1,-1) - - if ((e.State & DrawItemState.Selected) == DrawItemState.Selected): - self.textBrush.Color = SystemColors.Highlight - g.FillRectangle(self.textBrush, myBounds) - self.textBrush.Color = SystemColors.HighlightText - else: - self.textBrush.Color = SystemColors.Window - g.FillRectangle(self.textBrush, myBounds) - self.textBrush.Color = SystemColors.WindowText - - g.DrawImage(self.icon, imageRect) - g.DrawString(sender.Items[e.Index], e.Font, self.textBrush, - PointF(textRect.X, textRect.Y)) - - # Border line (note that Bottom is actually outside our bounds) - g.DrawLine(Pens.LightGray, - Point(e.Bounds.Left,e.Bounds.Bottom-1), - Point(e.Bounds.Right,e.Bounds.Bottom-1)) - e.DrawFocusRectangle() - - # --------------------------------- - - def UpdateAllItems(self, contents, keepSelection=False): - if keepSelection: - currentSelection = self.SelectedIndex - - formattedItems = [] - self.DataSource = None - - for moduleFullName in contents: - itemInfo = self.moduleManager.GetDocs(moduleFullName) - if itemInfo: - # Issue #20 - only display the base name of the module - # in the main UI. - displayName = moduleFullName.split(".", 1)[1] - composedString = "%s, version %s\n%s" %(displayName, - str(itemInfo[FTM_Version]), - itemInfo[FTM_Synopsis]) - if itemInfo[FTM_ModifiesDB]: - composedString += "\nNote: Can modify the project. (Use the 'Run (Modify)' buttons to make changes.) " - if itemInfo[FTM_HasConfig]: - composedString += "\nNote: This module has configurable parameters" - else: - composedString = "\nModule '%s' missing or failed to import!\n" \ - % moduleFullName - formattedItems.append(composedString) - self.DataSource = formattedItems - - if keepSelection: - self.SelectedIndex = currentSelection - - - def SetSelectedHandler(self, handler): - self.__SelectedHandler = handler - self.ItemSelectionChanged += self.__ItemSelectedHandler - - def __ItemSelectedHandler(self, sender, event): - if event.IsSelected: - if self.__SelectedHandler: - self.__SelectedHandler(event.Item.Text) - - def SetActivatedHandler(self, handler): - if handler: - self.__ActivatedHandler = handler - # Double-click and Enter will Run this module - self.DoubleClick += self.__ItemActivatedHandler - self.KeyDown += self.__ItemActivatedHandler - - def __ItemActivatedHandler(self, sender, event): - if self.__ActivatedHandler: - activate = True # For DoubleClick - if type(event) == KeyEventArgs: - activate = (event.KeyCode == Keys.Return) - if activate: - self.__ActivatedHandler() +# +# Project: FlexTools +# Module: UIModulesList +# Platform: .NET v2 Windows.Forms (Python.NET 2.5) +# +# A custom list for selecting the modules in the main FLexTools UI. +# +# +# Copyright Craig Farrow, 2010 - 2022 +# + +from __future__ import unicode_literals +from builtins import str + +from . import UIGlobal +from .FTModuleClass import * + +from System.Drawing import (Color, SystemColors, Point, PointF, Rectangle, + Size, Bitmap, Image, Icon, SystemIcons, + SolidBrush, Pens, Font, FontStyle, FontFamily) + +from System.Windows.Forms import ( + ListBox, DrawMode, + Button, + DockStyle, Orientation, View, SortOrder, + HorizontalAlignment, ImageList, + DrawItemState, DrawItemEventArgs, + KeyEventArgs, KeyPressEventArgs, Keys, + TextRenderer) + +# ------------------------------------------------------------------ + + +class ModulesList(ListBox): + SPACING = 1 + ICON_SPACE = 34 + + def __init__(self, moduleManager, contents): + ListBox.__init__(self) + + self.moduleManager = moduleManager + + # behaviour + self.LabelEdit = False + self.MultiSelect = False + + # appearance + self.Dock = DockStyle.Fill + self.ScrollAlwaysVisible = True + self.TabIndex = 0 + self.HideSelection = False # Keep selected item grey when lost focus + + self.textBrush = SolidBrush(SystemColors.WindowText) + self.icon = Bitmap(UIGlobal.ModuleIcon) + + self.DrawMode = DrawMode.OwnerDrawVariable + self.DrawItem += self.OnDrawItem + self.MeasureItem += self.OnMeasureItem + + self.UpdateAllItems(contents) + + # ---- Custom drawing methods ---- + + def __countLines(self, s): + return len(s.split("\n")) + + def OnMeasureItem(self, sender, e): + #print "Measuring:", sender.Items[e.Index] + if e.Index < 0: return + textHeight = self.Font.Height * self.__countLines(sender.Items[e.Index]) + e.ItemHeight = textHeight + 2 + self.SPACING # extra for separator line + + def OnDrawItem(self, sender, e): + if e.Index < 0: return + + g = e.Graphics + + ## Custom draw... an icon and multi-line text + + # Use our own size to preserve the border line + myBounds = e.Bounds + myBounds.Height -= self.SPACING + + imageRect = myBounds.MemberwiseClone() + imageRect.Inflate(-1,-1) + imageRect.Width = self.icon.Width + imageRect.X += (self.ICON_SPACE - self.icon.Width) / 2 + imageRect.Y += (imageRect.Height - self.icon.Height) / 2 + imageRect.Height = self.icon.Height + + textRect = myBounds.MemberwiseClone() + textRect.X += self.ICON_SPACE + textRect.Width -= self.ICON_SPACE + textRect.Height -= 1 + textRect.Inflate(-1,-1) + + if ((e.State & DrawItemState.Selected) == DrawItemState.Selected): + self.textBrush.Color = SystemColors.Highlight + g.FillRectangle(self.textBrush, myBounds) + self.textBrush.Color = SystemColors.HighlightText + else: + self.textBrush.Color = SystemColors.Window + g.FillRectangle(self.textBrush, myBounds) + self.textBrush.Color = SystemColors.WindowText + + g.DrawImage(self.icon, imageRect) + g.DrawString(sender.Items[e.Index], e.Font, self.textBrush, + PointF(textRect.X, textRect.Y)) + + # Border line (note that Bottom is actually outside our bounds) + g.DrawLine(Pens.LightGray, + Point(e.Bounds.Left,e.Bounds.Bottom-1), + Point(e.Bounds.Right,e.Bounds.Bottom-1)) + e.DrawFocusRectangle() + + # --------------------------------- + + def UpdateAllItems(self, contents, keepSelection=False): + if keepSelection: + currentSelection = self.SelectedIndex + + formattedItems = [] + self.DataSource = None + + for moduleFullName in contents: + itemInfo = self.moduleManager.GetDocs(moduleFullName) + if itemInfo: + # Issue #20 - only display the base name of the module + # in the main UI. + displayName = moduleFullName.split(".", 1)[1] + composedString = "%s, version %s\n%s" %(displayName, + str(itemInfo[FTM_Version]), + itemInfo[FTM_Synopsis]) + if itemInfo[FTM_ModifiesDB]: + composedString += "\nNote: Can modify the project. (Use the 'Run (Modify)' buttons to make changes.) " + if itemInfo[FTM_HasConfig]: + composedString += "\nNote: This module has configurable parameters" + else: + composedString = "\nModule '%s' missing or failed to import!\n" \ + % moduleFullName + formattedItems.append(composedString) + self.DataSource = formattedItems + + if keepSelection: + self.SelectedIndex = currentSelection + + + def SetSelectedHandler(self, handler): + self.__SelectedHandler = handler + self.ItemSelectionChanged += self.__ItemSelectedHandler + + def __ItemSelectedHandler(self, sender, event): + if event.IsSelected: + if self.__SelectedHandler: + self.__SelectedHandler(event.Item.Text) + + def SetActivatedHandler(self, handler): + if handler: + self.__ActivatedHandler = handler + # Double-click and Enter will Run this module + self.DoubleClick += self.__ItemActivatedHandler + self.KeyDown += self.__ItemActivatedHandler + + def __ItemActivatedHandler(self, sender, event): + if self.__ActivatedHandler: + activate = True # For DoubleClick + if type(event) == KeyEventArgs: + activate = (event.KeyCode == Keys.Return) + if activate: + self.__ActivatedHandler() diff --git a/FlexTools/UIProjectChooser.py b/flextoolslib/code/UIProjectChooser.py similarity index 98% rename from FlexTools/UIProjectChooser.py rename to flextoolslib/code/UIProjectChooser.py index 8009377..65affa2 100644 --- a/FlexTools/UIProjectChooser.py +++ b/flextoolslib/code/UIProjectChooser.py @@ -6,10 +6,10 @@ # Dialog for selecting a Fieldworks project. # # -# Copyright Craig Farrow, 2010 - 2018 +# Copyright Craig Farrow, 2010 - 2022 # -import UIGlobal +from . import UIGlobal from flexlibs import FLExInitialize, FLExCleanup from flexlibs import AllProjectNames diff --git a/FlexTools/UIReport.py b/flextoolslib/code/UIReport.py similarity index 98% rename from FlexTools/UIReport.py rename to flextoolslib/code/UIReport.py index 5aba1dc..b266c1a 100644 --- a/FlexTools/UIReport.py +++ b/flextoolslib/code/UIReport.py @@ -14,8 +14,8 @@ import os -import UIGlobal -import FTReport +from . import UIGlobal +from . import FTReport from System.Drawing import ( Size, Bitmap, Image, Icon, diff --git a/flextoolslib/code/__init__.py b/flextoolslib/code/__init__.py new file mode 100644 index 0000000..a6b4237 --- /dev/null +++ b/flextoolslib/code/__init__.py @@ -0,0 +1,3 @@ +#---------------------------------------------------------------------------- +# Name: __init__.py +#---------------------------------------------------------------------------- diff --git a/flextoolslib/docs/FLExTools Help.pdf b/flextoolslib/docs/FLExTools Help.pdf new file mode 100644 index 0000000..07342e5 Binary files /dev/null and b/flextoolslib/docs/FLExTools Help.pdf differ diff --git a/flextoolslib/docs/FLExTools Programming.pdf b/flextoolslib/docs/FLExTools Programming.pdf new file mode 100644 index 0000000..e9f5bd1 Binary files /dev/null and b/flextoolslib/docs/FLExTools Programming.pdf differ diff --git a/FlexTools/__icons/Flextools.ico b/flextoolslib/icons/Flextools.ico similarity index 100% rename from FlexTools/__icons/Flextools.ico rename to flextoolslib/icons/Flextools.ico diff --git a/FlexTools/__icons/Fugue/cross_circle.png b/flextoolslib/icons/Fugue/cross_circle.png similarity index 100% rename from FlexTools/__icons/Fugue/cross_circle.png rename to flextoolslib/icons/Fugue/cross_circle.png diff --git a/FlexTools/__icons/Fugue/exclamation.png b/flextoolslib/icons/Fugue/exclamation.png similarity index 100% rename from FlexTools/__icons/Fugue/exclamation.png rename to flextoolslib/icons/Fugue/exclamation.png diff --git a/FlexTools/__icons/Fugue/information.png b/flextoolslib/icons/Fugue/information.png similarity index 100% rename from FlexTools/__icons/Fugue/information.png rename to flextoolslib/icons/Fugue/information.png diff --git a/FlexTools/__icons/IconBuffet Redmond Studio/Flex_16.gif b/flextoolslib/icons/IconBuffet Redmond Studio/Flex_16.gif similarity index 100% rename from FlexTools/__icons/IconBuffet Redmond Studio/Flex_16.gif rename to flextoolslib/icons/IconBuffet Redmond Studio/Flex_16.gif diff --git a/FlexTools/__icons/IconBuffet Redmond Studio/add_16.gif b/flextoolslib/icons/IconBuffet Redmond Studio/add_16.gif similarity index 100% rename from FlexTools/__icons/IconBuffet Redmond Studio/add_16.gif rename to flextoolslib/icons/IconBuffet Redmond Studio/add_16.gif diff --git a/FlexTools/__icons/IconBuffet Redmond Studio/applications_16.gif b/flextoolslib/icons/IconBuffet Redmond Studio/applications_16.gif similarity index 100% rename from FlexTools/__icons/IconBuffet Redmond Studio/applications_16.gif rename to flextoolslib/icons/IconBuffet Redmond Studio/applications_16.gif diff --git a/FlexTools/__icons/IconBuffet Redmond Studio/arrow-down_16.gif b/flextoolslib/icons/IconBuffet Redmond Studio/arrow-down_16.gif similarity index 100% rename from FlexTools/__icons/IconBuffet Redmond Studio/arrow-down_16.gif rename to flextoolslib/icons/IconBuffet Redmond Studio/arrow-down_16.gif diff --git a/FlexTools/__icons/IconBuffet Redmond Studio/arrow-forward-!_16.gif b/flextoolslib/icons/IconBuffet Redmond Studio/arrow-forward-!_16.gif similarity index 100% rename from FlexTools/__icons/IconBuffet Redmond Studio/arrow-forward-!_16.gif rename to flextoolslib/icons/IconBuffet Redmond Studio/arrow-forward-!_16.gif diff --git a/FlexTools/__icons/IconBuffet Redmond Studio/arrow-forward-double-!_16.gif b/flextoolslib/icons/IconBuffet Redmond Studio/arrow-forward-double-!_16.gif similarity index 100% rename from FlexTools/__icons/IconBuffet Redmond Studio/arrow-forward-double-!_16.gif rename to flextoolslib/icons/IconBuffet Redmond Studio/arrow-forward-double-!_16.gif diff --git a/FlexTools/__icons/IconBuffet Redmond Studio/arrow-forward-double_16.gif b/flextoolslib/icons/IconBuffet Redmond Studio/arrow-forward-double_16.gif similarity index 100% rename from FlexTools/__icons/IconBuffet Redmond Studio/arrow-forward-double_16.gif rename to flextoolslib/icons/IconBuffet Redmond Studio/arrow-forward-double_16.gif diff --git a/FlexTools/__icons/IconBuffet Redmond Studio/arrow-forward_16.gif b/flextoolslib/icons/IconBuffet Redmond Studio/arrow-forward_16.gif similarity index 100% rename from FlexTools/__icons/IconBuffet Redmond Studio/arrow-forward_16.gif rename to flextoolslib/icons/IconBuffet Redmond Studio/arrow-forward_16.gif diff --git a/FlexTools/__icons/IconBuffet Redmond Studio/arrow-up_16.gif b/flextoolslib/icons/IconBuffet Redmond Studio/arrow-up_16.gif similarity index 100% rename from FlexTools/__icons/IconBuffet Redmond Studio/arrow-up_16.gif rename to flextoolslib/icons/IconBuffet Redmond Studio/arrow-up_16.gif diff --git a/FlexTools/__icons/IconBuffet Redmond Studio/copy_16.gif b/flextoolslib/icons/IconBuffet Redmond Studio/copy_16.gif similarity index 100% rename from FlexTools/__icons/IconBuffet Redmond Studio/copy_16.gif rename to flextoolslib/icons/IconBuffet Redmond Studio/copy_16.gif diff --git a/FlexTools/__icons/IconBuffet Redmond Studio/delete_16.gif b/flextoolslib/icons/IconBuffet Redmond Studio/delete_16.gif similarity index 100% rename from FlexTools/__icons/IconBuffet Redmond Studio/delete_16.gif rename to flextoolslib/icons/IconBuffet Redmond Studio/delete_16.gif diff --git a/FlexTools/__icons/IconBuffet Redmond Studio/documents_16.gif b/flextoolslib/icons/IconBuffet Redmond Studio/documents_16.gif similarity index 100% rename from FlexTools/__icons/IconBuffet Redmond Studio/documents_16.gif rename to flextoolslib/icons/IconBuffet Redmond Studio/documents_16.gif diff --git a/FlexTools/__icons/Module.ico b/flextoolslib/icons/Module.ico similarity index 100% rename from FlexTools/__icons/Module.ico rename to flextoolslib/icons/Module.ico diff --git a/FlexTools/__icons/readme.txt b/flextoolslib/icons/readme.txt similarity index 96% rename from FlexTools/__icons/readme.txt rename to flextoolslib/icons/readme.txt index 51e0bfb..3f04712 100644 --- a/FlexTools/__icons/readme.txt +++ b/flextoolslib/icons/readme.txt @@ -1,13 +1,13 @@ - -The icons used in this project are: - -IconBuffet - http://www.iconbuffet.com/ - Copyright Firewheel Design, used with permission. - -Fugue Icons 0.3.5 (Beta) - http://iconzworld.com/fugue-png-icon-set.html - Copyright 2008 Yusuke Kamiyamane. All rights reserved. - Licensed under a Creative Commons Attribution 3.0 licence. - + +The icons used in this project are: + +IconBuffet + http://www.iconbuffet.com/ + Copyright Firewheel Design, used with permission. + +Fugue Icons 0.3.5 (Beta) + http://iconzworld.com/fugue-png-icon-set.html + Copyright 2008 Yusuke Kamiyamane. All rights reserved. + Licensed under a Creative Commons Attribution 3.0 licence. + \ No newline at end of file diff --git a/flextoolslib/misc/RunModule.py b/flextoolslib/misc/RunModule.py new file mode 100644 index 0000000..be51810 --- /dev/null +++ b/flextoolslib/misc/RunModule.py @@ -0,0 +1,129 @@ +# +# RunModule +# +# A test frame for developing a new Module. +# Used by FlexTools\scripts\TestAModule.py +# + +import os +import sys +import imp +# import importlib - TODO: switch to using importlib +import traceback + +import logging +logger = logging.getLogger(__name__) + +from flexlibs import FLExInitialize, FLExCleanup +from flexlibs import ( + FLExProject, + FP_ProjectError, + FP_FileNotFoundError) + +from ..code.FTModuleClass import FTM_ModuleError +from ..code.FTReport import FTReporter + + +#---------------------------------------------------------------- + +def __ImportModule(moduleFolderAndName): + # + # Import the module + # + + modulePath, moduleName = os.path.split(moduleFolderAndName) + absPath = os.path.abspath(moduleFolderAndName) + moduleName, _ = os.path.splitext(moduleName) + libPath, _ = os.path.split(absPath) + + logger.debug(absPath) + + # Append the local path in case there are local dependencies + sys.path.append(libPath) + + logger.info(f"Importing {libPath}::{moduleName}") + + try: + fp, pathname, description = imp.find_module(moduleName, [libPath]) + except ImportError as e: + logger.error(f"Can't find module '{moduleFolderAndName}'!") + return None + + try: + module = imp.load_module(moduleName, fp, pathname, description) + except FTM_ModuleError as e: + msg = f"{moduleFolderAndName}:\n{e.message}" + logger.error(msg) + return None + except: + msg = f"\nException importing module {pathname}\n{traceback.format_exc()}" + logger.error(msg) + return None + finally: + # Since we may exit via an exception, close fp explicitly. + if fp: + fp.close() + + try: + ftm = module.FlexToolsModule + except AttributeError: + logger.error (f"FlexToolsModule object not found in {moduleFolderAndName}.") + return None + + return ftm + + +#---------------------------------------------------------------- + +def __RunModule(module, project): + + # --- Import the module --- + ftm = __ImportModule(module) + if not ftm: + return False + + logger.info (f"Module info:\n{ftm.Help()}") + + # --- Open the project --- + logger.info(f"Opening project '{project}'") + FlexDB = FLExProject() + + try: + FlexDB.OpenProject(projectName = project) + logger.info("OK") + except FP_FileNotFoundError as e: + logger.error(f"Project '{project}' not found!") + return False + except FP_ProjectError as e: + logger.error(f"Error opening project!\n{e.message}") + return False + + # --- Run the module --- + reporter = FTReporter() + try: + ftm.Run(FlexDB, reporter) + except: + logger.exception("Runtime error:") + return False + + TYPE_LOOKUP = ["INFO", "WARN", "ERR ", " "] + for m in reporter.messages: + msgType, msg, ref = m + logger.info (f"{TYPE_LOOKUP[msgType]}: {msg}") + if ref: + logger.info (f">>>> {ref}") + + FlexDB.CloseProject() + + return True + + +def RunModule(module, project): + + FLExInitialize() + + result = __RunModule(module, project) + + FLExCleanup() + + return result diff --git a/flextoolslib/misc/__init__.py b/flextoolslib/misc/__init__.py new file mode 100644 index 0000000..a6b4237 --- /dev/null +++ b/flextoolslib/misc/__init__.py @@ -0,0 +1,3 @@ +#---------------------------------------------------------------------------- +# Name: __init__.py +#---------------------------------------------------------------------------- diff --git a/history.txt b/history.txt index c529210..fa19b5e 100644 --- a/history.txt +++ b/history.txt @@ -5,11 +5,20 @@ FLExTools History ==== Known Issues ==== * Requires Python <= 3.8 -* Doesn't work with FieldWorks >= 9.1.15 ====== History ======= +2.2.0_2023.01.31 +---------------- + ++ Works with FieldWorks up to 9.1.18 ++ The main application was split off into a separate library called + flextoolslib, which is installed from PyPI. The Modules and launch + scripts are distributed as FlexTools.zip. ++ Issue #11: Changed warning message on Run (Modify) to an Info message. + + 2.1.2 - 15 Oct 2022 ------------------- diff --git a/make.bat b/make.bat new file mode 100644 index 0000000..b32d21c --- /dev/null +++ b/make.bat @@ -0,0 +1,46 @@ +@ECHO OFF +REM Simple build commands for flextools + +set PYTHON=py -3.8 + +REM Check that the argument is a valid command, and do it. /I ignores case. +FOR %%C IN ("Init" + "Clean" + "Build" + "Publish") DO ( + IF /I "%1"=="%%~C" GOTO :Do%1 +) + +:Usage + echo Usage: + echo make init - Install the libraries for building + echo make clean - Clean out build files + echo make build - Build the project + echo make publish - Publish the project to PyPI + goto :End + +:DoInit + %PYTHON% -m pip install -r requirements.txt + %PYTHON% -m pip install -r requirements-dev.txt + goto :End + +:DoClean + rmdir /s /q ".\build" + rmdir /s /q ".\dist" + goto :End + +:DoBuild + @REM Build the wheel for flextoolslibs with setuptools + %PYTHON% -m build -w + + @REM Build the main FlexTools zip file + %PYTHON% makezip.py + goto :End + +:DoPublish + echo Publishing wheel to PyPI + %PYTHON% -m twine upload .\dist\flextoolslib* + goto :End + + +:End diff --git a/makezip.py b/makezip.py new file mode 100644 index 0000000..1c11976 --- /dev/null +++ b/makezip.py @@ -0,0 +1,58 @@ +# +# makezip.py +# +# Package the FlexTools folder into a zip file for distribution. +# +# Copyright Craig Farrow, 2022 +# + +import sys +sys.path.append("FlexTools/scripts/") + +from Version import FlexToolsVersion + +import pathlib +from zipfile import ZipFile, ZIP_DEFLATED + + +# FlexTools\ contains the following files that we need to package up: +# - .vbs, .bat & .py scripts, including the scripts\ folder +# - flextools.ini - a default configuration +# - Modules and Collections folders +# + +FTPath = pathlib.Path("FlexTools") + +ZIPFILE_NAME = f"dist\\FlexTools_{FlexToolsVersion}.zip" + +PATH_PATTERNS = ( + "*.vbs", + "scripts/*", + "Modules/**/*", # "**" = recursive + "Collections/*", + ) + +FILTERED_SUFFIXES = (".pyd", ".pyc", ".bak", ".log", ".doc", ".tmp") + +#----------------------------------------------------------- + +def includeFile(path): + if path.stem == "__pycache__": + return False + # Filter out the Archive folder in the Chinese modules + if path.name == "Archive": + return False + if path.is_file() and path.parent.name == "Archive": + return False + + return not path.suffix in FILTERED_SUFFIXES + + +print (f"Creating archive {ZIPFILE_NAME}") + +with ZipFile(ZIPFILE_NAME, 'w', ZIP_DEFLATED) as z: + for pathPattern in PATH_PATTERNS: + for fn in filter(includeFile, FTPath.glob(pathPattern)): + print(f"adding '{fn}'") + z.write(fn) + diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..b55b7e1 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,5 @@ +[build-system] +requires = [ + "setuptools>=43.0.0", + ] +build-backend = "setuptools.build_meta" diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..0c5c0a8 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,5 @@ +# Libraries required for building and publishing flextools. +pytest +build +twine + diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index f3d68a8..0000000 --- a/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -configparser -cdfutils -flexlibs>=1.1.5 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..dcda2a2 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,52 @@ +[metadata] +name = flextoolslib +version = attr: flextoolslib.version +description = A tool for running Python scripts on FieldWorks Language Explorer projects +long_description = file: README.md +url = https://github.com/cdfarrow/flextools +license_files = LICENSE.txt + +author = Craig Farrow +author_email = flextoolshelp@gmail.com + +platforms = ['Windows', 'Linux'] +classifiers = + Intended Audience :: Developers + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Topic :: Text Processing :: Linguistic + Topic :: Database + License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+) + + +[options] +python_requires = >=3.6, <3.9 + + +# The libraries needed at runtime. +install_requires = + flexlibs>=1.1.6 + cdfutils>=1.0.4 + +# Tell setuptools about our Python package found in the folder +# 'flextoolslib', and configure it to be installed as 'flextoolslib' +# (i.e. into site-packages/flextoolslib). +# This will contain the sub-folders from the flextoolslib folder: +# +# flextoolslib +# ├───code +# ├───docs +# └───icons +# + +package_dir = + flextoolslib = flextoolslib + +# The docs and icons folders are specified in MANIFEST.in +# 'include_package_data = True' includes them in the wheel. +include_package_data = True + +# Specify which Python versions we support: +[bdist_wheel] +python-tag=py36.py37.py38