summaryrefslogtreecommitdiff
path: root/inkstitch.py
diff options
context:
space:
mode:
authorkarnigen <karnigen@gmail.com>2024-01-12 19:01:22 +0100
committerGitHub <noreply@github.com>2024-01-12 19:01:22 +0100
commitbc991aaa250339ea2d1ca96c9d531e39d8027ab7 (patch)
treeb93c665a4468fb6922dc9847ba012c864879b739 /inkstitch.py
parent0673df568351fd8ac50a4d2f5b27b91c29b8961d (diff)
parent78a3c93fe3b75f9aeb315f13f6ccd925230672c2 (diff)
Merge pull request #2653 from inkstitch/kgn/debug_profile_extend_vscode
Kgn/debug profile extend vscode
Diffstat (limited to 'inkstitch.py')
-rw-r--r--inkstitch.py187
1 files changed, 131 insertions, 56 deletions
diff --git a/inkstitch.py b/inkstitch.py
index 91a0f18a..d85eaba4 100644
--- a/inkstitch.py
+++ b/inkstitch.py
@@ -2,84 +2,159 @@
#
# Copyright (c) 2010 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
-import cProfile
-import pstats
-import logging
+
import os
import sys
-from argparse import ArgumentParser
-from io import StringIO
+from pathlib import Path # to work with paths as objects
+import configparser # to read DEBUG.ini
+
+import lib.debug_utils as debug_utils
+
+SCRIPTDIR = Path(__file__).parent.absolute()
+
+running_as_frozen = getattr(sys, 'frozen', None) is not None # check if running from pyinstaller bundle
+
+if len(sys.argv) < 2:
+ # no arguments - prevent accidentally running this script
+ msg = "No arguments given, exiting!" # without gettext localization see _()
+ if running_as_frozen: # we show dialog only when running from pyinstaller bundle - using wx
+ try:
+ import wx
+ app = wx.App()
+ dlg = wx.MessageDialog(None, msg, "Inkstitch", wx.OK | wx.ICON_ERROR)
+ dlg.ShowModal()
+ dlg.Destroy()
+ except ImportError:
+ print(msg)
+ else:
+ print(msg)
+ exit(1)
-from lib.exceptions import InkstitchException, format_uncaught_exception
+ini = configparser.ConfigParser()
+ini.read(SCRIPTDIR / "DEBUG.ini") # read DEBUG.ini file if exists
-if getattr(sys, 'frozen', None) is None:
- # When running in development mode, we want to use the inkex installed by
- # pip install, not the one bundled with Inkscape which is not new enough.
- if sys.platform == "darwin":
- extensions_path = "/Applications/Inkscape.app/Contents/Resources/share/inkscape/extensions"
+# check if running from inkscape, given by environment variable
+if os.environ.get('INKSTITCH_OFFLINE_SCRIPT', '').lower() in ['true', '1', 'yes', 'y']:
+ running_from_inkscape = False
+else:
+ running_from_inkscape = True
+
+debug_active = bool((gettrace := getattr(sys, 'gettrace')) and gettrace()) # check if debugger is active on startup
+debug_type = 'none'
+profiler_type = 'none'
+
+if not running_as_frozen: # debugging/profiling only in development mode
+ # specify debugger type
+ # - if script was already started from debugger then don't read debug type from ini file or cmd line
+ if not debug_active:
+ # enable/disable debugger from bash: -d
+ if os.environ.get('INKSTITCH_DEBUG_ENABLE', '').lower() in ['true', '1', 'yes', 'y']:
+ debug_enable = True
+ else:
+ debug_enable = ini.getboolean("DEBUG","debug_enable", fallback=False) # enable debugger on startup from ini
+
+ debug_type = ini.get("DEBUG","debug_type", fallback="none") # debugger type vscode, pycharm, pydevd
+ if not debug_enable:
+ debug_type = 'none'
+
+ debug_to_file = ini.getboolean("DEBUG","debug_to_file", fallback=False) # write debug output to file
+ if debug_to_file and debug_type == 'none':
+ debug_type = 'file'
+
+ # enbale/disable profiling from bash: -p
+ if os.environ.get('INKSTITCH_PROFILE_ENABLE', '').lower() in ['true', '1', 'yes', 'y']:
+ profile_enable = True
else:
- extensions_path = "/usr/share/inkscape/extensions"
+ profile_enable = ini.getboolean("PROFILE","profile_enable", fallback=False) # read from ini
+
+ # specify profiler type
+ profiler_type = ini.get("PROFILE","profiler_type", fallback="none") # profiler type cprofile, profile, pyinstrument
+ if not profile_enable:
+ profiler_type = 'none'
+
+ if running_from_inkscape:
+ # process creation of the Bash script - should be done before sys.path is modified, see below in prefere_pip_inkex
+ if ini.getboolean("DEBUG","create_bash_script", fallback=False): # create script only if enabled in DEBUG.ini
+ debug_utils.write_offline_debug_script(SCRIPTDIR, ini)
+
+ # disable debugger when running from inkscape
+ disable_from_inkscape = ini.getboolean("DEBUG","disable_from_inkscape", fallback=False)
+ if disable_from_inkscape:
+ debug_type = 'none' # do not start debugger when running from inkscape
+
+ # prefer pip installed inkex over inkscape bundled inkex, pip version is bundled with Inkstitch
+ # - must be be done before importing inkex
+ prefere_pip_inkex = ini.getboolean("LIBRARY","prefer_pip_inkex", fallback=True)
+ if prefere_pip_inkex and 'PYTHONPATH' in os.environ:
+ debug_utils.reorder_sys_path()
+
+from argparse import ArgumentParser # to parse arguments and remove --extension
+import logging # to set logger for shapely
+from io import StringIO # to store shapely errors
+
+from lib.exceptions import InkstitchException, format_uncaught_exception
+
+from inkex import errormsg # to show error message in inkscape
+from lxml.etree import XMLSyntaxError # to catch XMLSyntaxError from inkex
- sys.path.remove(extensions_path)
- sys.path.append(extensions_path)
+from lib.debug import debug # import global variable debug - don't import whole module
-from inkex import errormsg
-from lxml.etree import XMLSyntaxError
+from lib import extensions # import all supported extensions of institch
+from lib.i18n import _ # see gettext translation function _()
+from lib.utils import restore_stderr, save_stderr # to hide GTK spam
-import lib.debug as debug
-from lib import extensions
-from lib.i18n import _
-from lib.utils import restore_stderr, save_stderr
+# enabling of debug depends on value of debug_type in DEBUG.ini file
+if debug_type != 'none':
+ debug.enable(debug_type, SCRIPTDIR, ini)
+ # check if debugger is really activated
+ debug_active = bool((gettrace := getattr(sys, 'gettrace')) and gettrace())
-# ignore warnings in releases
-if getattr(sys, 'frozen', None):
+# warnings are used by some modules, we want to ignore them all in release
+# - see warnings.warn()
+if running_as_frozen or not debug_active:
import warnings
warnings.filterwarnings('ignore')
-logger = logging.getLogger('shapely.geos')
-logger.setLevel(logging.DEBUG)
-shapely_errors = StringIO()
-ch = logging.StreamHandler(shapely_errors)
-ch.setLevel(logging.DEBUG)
-formatter = logging.Formatter('%(name)s - %(levelname)s - %(message)s')
-ch.setFormatter(formatter)
-logger.addHandler(ch)
-
+# TODO - check if this is still needed for shapely, apparently shapely now uses only exceptions instead of io.
+# all logs were removed from version 2.0.0 and above
+
+# ---- plan to remove this in future ----
+# set logger for shapely - for old versions of shapely
+# logger = logging.getLogger('shapely.geos') # attach logger of shapely
+# logger.setLevel(logging.DEBUG)
+# shapely_errors = StringIO() # in memory file to store shapely errors
+# ch = logging.StreamHandler(shapely_errors)
+# ch.setLevel(logging.DEBUG)
+# formatter = logging.Formatter('%(name)s - %(levelname)s - %(message)s')
+# ch.setFormatter(formatter)
+# logger.addHandler(ch)
+# ---- plan to remove this in future ----
+
+# pop '--extension' from arguments and generate extension class name from extension name
+# example: --extension=params will instantiate Params() class from lib.extensions.
parser = ArgumentParser()
parser.add_argument("--extension")
my_args, remaining_args = parser.parse_known_args()
-if os.path.exists(os.path.join(os.path.dirname(os.path.realpath(__file__)), "DEBUG")):
- debug.enable()
-
-profiler = None
-if os.path.exists(os.path.join(os.path.dirname(os.path.realpath(__file__)), "PROFILE")):
- profiler = cProfile.Profile()
- profiler.enable()
-
extension_name = my_args.extension
# example: foo_bar_baz -> FooBarBaz
extension_class_name = extension_name.title().replace("_", "")
extension_class = getattr(extensions, extension_class_name)
-extension = extension_class()
+extension = extension_class() # create instance of extension class - call __init__ method
-if (hasattr(sys, 'gettrace') and sys.gettrace()) or profiler is not None:
- extension.run(args=remaining_args)
- if profiler:
- path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "profile_stats")
- profiler.disable()
- profiler.dump_stats(path + ".prof")
-
- with open(path, 'w') as stats_file:
- stats = pstats.Stats(profiler, stream=stats_file)
- stats.sort_stats(pstats.SortKey.CUMULATIVE)
- stats.print_stats()
+# extension run(), we differentiate between debug and normal mode
+# - in debug or profile mode we debug or profile extension.run() method
+# - in normal mode we run extension.run() in try/except block to catch all exceptions and hide GTK spam
+if debug_active or profiler_type != "none": # if debug or profile mode
+ if profiler_type == 'none': # only debugging
+ extension.run(args=remaining_args)
+ else: # do profiling
+ debug_utils.profile(profiler_type, SCRIPTDIR, ini, extension, remaining_args)
- print(f"profiling stats written to {path} and {path}.prof", file=sys.stderr)
-else:
- save_stderr()
+else: # if not debug nor profile mode
+ save_stderr() # hide GTK spam
exception = None
try:
extension.run(args=remaining_args)
@@ -99,7 +174,7 @@ else:
finally:
restore_stderr()
- if shapely_errors.tell():
- errormsg(shapely_errors.getvalue())
+ # if shapely_errors.tell(): # see above plan to remove this in future for shapely
+ # errormsg(shapely_errors.getvalue())
sys.exit(0)