summaryrefslogtreecommitdiff
path: root/inkstitch.py
diff options
context:
space:
mode:
Diffstat (limited to 'inkstitch.py')
-rw-r--r--inkstitch.py167
1 files changed, 131 insertions, 36 deletions
diff --git a/inkstitch.py b/inkstitch.py
index 91a0f18a..e5ef3d7a 100644
--- a/inkstitch.py
+++ b/inkstitch.py
@@ -2,27 +2,82 @@
#
# 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
+import lib.debug_utils as debug_utils
+from pathlib import Path
+
+SCRIPTDIR = Path(__file__).parent.absolute()
+
+if len(sys.argv) < 2:
+ exit(1) # no arguments - prevent uncidentally running this script
+
+running_as_frozen = getattr(sys, 'frozen', None) is not None # check if running from pyinstaller bundle
+running_from_inkscape = '.ink.svg' not in sys.argv # inkscape never starts extension with .ink.svg file in args
+# running_from_inkscape = True # for testing
+
+debug_active = bool((gettrace := getattr(sys, 'gettrace')) and gettrace()) # check if debugger is active on startup
+debug_file = SCRIPTDIR / "DEBUG"
+debug_type = 'none'
+
+profile_file = SCRIPTDIR / "PROFILE"
+profile_type = 'none'
+
+# print(f"debug_type:'{debug_type}' profile_type:'{profile_type}'", file=sys.stderr) # for testing
+
+# if script was already started from debugger then don't read debug file
+if not running_as_frozen and not debug_active and os.path.exists(debug_file):
+ debug_type = debug_utils.parse_file(debug_file) # read type of debugger from debug_file DEBUG
+ if debug_type == 'none': # for better backward compatibility
+ print(f"Debug file exists but no debugger type found in '{debug_file.name}'", file=sys.stderr)
+
+if os.path.exists(profile_file):
+ profile_type = debug_utils.parse_file(profile_file) # read type of profiler from profile_file PROFILE
+ if profile_type == 'none': # for better backward compatibility
+ print(f"Profile file exists but no profiler type found in '{profile_file.name}'", file=sys.stderr)
+
+if running_from_inkscape:
+ if debug_type.endswith('-script'): # if offline debugging just create script for later debugging
+ debug_utils.write_offline_debug_script(SCRIPTDIR)
+ debug_type = 'none' # do not start debugger when running from inkscape
+else: # not running from inkscape
+ if debug_type.endswith('-script'): # remove '-script' to propely initialize debugger packages for each editor
+ debug_type = debug_type.replace('-script', '')
+
+if not running_as_frozen:
+ # When running in development mode, we prefer inkex installed by pip, not the one bundled with Inkscape.
+ # - move inkscape extensions path to the end of sys.path
+ # - we compare PYTHONPATH with sys.path and move PYTHONPATH to the end of sys.path
+ # - also user inkscape extensions path is moved to the end of sys.path - may cause problems?
+ # - path for deprecated-simple are removed from sys.path, will be added later by importing inkex
+
+ # PYTHONPATH to list
+ pythonpath = os.environ.get('PYTHONPATH', '').split(os.pathsep)
+ # remove pythonpath from sys.path
+ sys.path = [p for p in sys.path if p not in pythonpath]
+ # remove deprecated-simple, it will be added later by importing inkex
+ pythonpath = [p for p in pythonpath if not p.endswith('deprecated-simple')]
+ # add pythonpath to the end of sys.path
+ sys.path.extend(pythonpath)
+
+ # >> should be removed after previous code was tested <<
+ # if sys.platform == "darwin":
+ # extensions_path = "/Applications/Inkscape.app/Contents/Resources/share/inkscape/extensions" # Mac
+ # else:
+ # extensions_path = "/usr/share/inkscape/extensions" # Linux
+ # # windows not solved
+ # move inkscape extensions path to the end of sys.path
+ # sys.path.remove(extensions_path)
+ # sys.path.append(extensions_path)
+ # >> ------------------------------------------------- <<
+
+import logging
from argparse import ArgumentParser
from io import StringIO
from lib.exceptions import InkstitchException, format_uncaught_exception
-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"
- else:
- extensions_path = "/usr/share/inkscape/extensions"
-
- sys.path.remove(extensions_path)
- sys.path.append(extensions_path)
-
from inkex import errormsg
from lxml.etree import XMLSyntaxError
@@ -31,55 +86,95 @@ from lib import extensions
from lib.i18n import _
from lib.utils import restore_stderr, save_stderr
-# ignore warnings in releases
-if getattr(sys, 'frozen', None):
+# file DEBUG exists next to inkstitch.py - enabling debug mode depends on value of debug_type in DEBUG file
+if debug_type != 'none':
+ debug.enable(debug_type)
+ # check if debugger is really activated
+ debug_active = bool((gettrace := getattr(sys, 'gettrace')) and gettrace())
+
+# ignore warnings in releases - see warnings.warn()
+if running_as_frozen or not debug_active:
import warnings
warnings.filterwarnings('ignore')
-logger = logging.getLogger('shapely.geos')
+# set logger for shapely
+logger = logging.getLogger('shapely.geos') # attach logger of shapely, from ver 2.0.0 all logs are exceptions
logger.setLevel(logging.DEBUG)
-shapely_errors = StringIO()
+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)
+# pop '--extension' from arguments and generate extension class name from extension name
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
+
+# extension run(), but we differentiate between debug and normal mode
+# - in debug or profile mode we run extension or profile extension
+# - in normal mode we run extension in try/except block to catch all exceptions
+if debug_active or profile_type != "none": # if debug or profile mode
+ print(f"Extension:'{extension_name}' Debug active:{debug_active} type:'{debug_type}' "
+ f"Profile type:'{profile_type}'", file=sys.stderr)
+ profile_path = SCRIPTDIR / "profile_stats"
+
+ if profile_type == 'none':
+ extension.run(args=remaining_args)
+ elif profile_type == 'cprofile':
+ import cProfile
+ import pstats
+ profiler = cProfile.Profile()
-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.enable()
+ extension.run(args=remaining_args)
profiler.disable()
- profiler.dump_stats(path + ".prof")
- with open(path, 'w') as stats_file:
+ profiler.dump_stats(profile_path.with_suffix(".prof")) # can be read by 'snakeviz -s' or 'pyprof2calltree'
+ with open(profile_path, 'w') as stats_file:
stats = pstats.Stats(profiler, stream=stats_file)
stats.sort_stats(pstats.SortKey.CUMULATIVE)
stats.print_stats()
+ print(f"profiling stats written to '{profile_path.name}' and '{profile_path.name}.prof'", file=sys.stderr)
+
+ elif profile_type == 'profile':
+ import profile
+ import pstats
+ profiler = profile.Profile()
+
+ profiler.run('extension.run(args=remaining_args)')
+
+ profiler.dump_stats(profile_path.with_suffix(".prof")) # can be read by 'snakeviz' or 'pyprof2calltree' - seems broken
+ with open(profile_path, 'w') as stats_file:
+ stats = pstats.Stats(profiler, stream=stats_file)
+ stats.sort_stats(pstats.SortKey.CUMULATIVE)
+ stats.print_stats()
+ print(f"profiling stats written to '{profile_path.name}'", file=sys.stderr)
+
+ elif profile_type == 'pyinstrument':
+ import pyinstrument
+ profiler = pyinstrument.Profiler()
+
+ profiler.start()
+ extension.run(args=remaining_args)
+ profiler.stop()
+
+ profile_path = SCRIPTDIR / "profile_stats.html"
+ with open(profile_path, 'w') as stats_file:
+ stats_file.write(profiler.output_html())
+ print(f"profiling stats written to '{profile_path.name}'", file=sys.stderr)
- 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)