kopia lustrzana https://github.com/inkstitch/inkstitch
initial changes
rodzic
732a6556de
commit
cc016b193e
|
@ -21,8 +21,11 @@ locales/
|
|||
.DS_Store
|
||||
/PROFILE
|
||||
/profile_stats
|
||||
/profile_stats.html
|
||||
/profile_stats.prof
|
||||
/.vscode
|
||||
__pycache__
|
||||
flaskserverport.json
|
||||
electron/yarn.lock
|
||||
.ink.sh
|
||||
.ink.svg
|
||||
|
|
167
inkstitch.py
167
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
|
||||
|
||||
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")
|
||||
# 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()
|
||||
|
||||
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)
|
||||
|
||||
print(f"profiling stats written to {path} and {path}.prof", file=sys.stderr)
|
||||
else:
|
||||
save_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)
|
||||
|
||||
else: # if not debug nor profile mode
|
||||
save_stderr() # hide GTK spam
|
||||
exception = None
|
||||
try:
|
||||
extension.run(args=remaining_args)
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
# Authors: see git history
|
||||
#
|
||||
# Copyright (c) 2010 Authors
|
||||
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# DEBUG file format:
|
||||
# - first non-comment line is debugger type
|
||||
# - valid values are:
|
||||
# "vscode" or "vscode-script" - for debugging with vscode
|
||||
# "pycharm" or "pycharm-script" - for debugging with pycharm
|
||||
# "pydev" or "pydev-script" - for debugging with pydev
|
||||
# "none" or empty file - for no debugging
|
||||
# - for offline debugging without inkscape, set debugger name to
|
||||
# as "vscode-script" or "pycharm-script" or "pydev-script"
|
||||
# - in that case running from inkscape will not start debugger
|
||||
# but prepare script for offline debugging from console
|
||||
# - backward compatibilty is broken due to confusion
|
||||
# debug_type = 'pydev' # default debugger backwards compatibility
|
||||
# if 'PYCHARM_REMOTE_DEBUG' in os.environ: # backwards compatibility
|
||||
# debug_type = 'pycharm'
|
||||
|
||||
# PROFILE file format:
|
||||
# - first non-comment line is profiler type
|
||||
# - valid values are:
|
||||
# "cprofile" - for cProfile
|
||||
# "pyinstrument" - for pyinstrument
|
||||
# "profile" - for profile
|
||||
# "none" - for no profiling
|
||||
|
||||
|
||||
def parse_file(filename):
|
||||
# parse DEBUG or PROFILE file for type
|
||||
# - return first noncomment and nonempty line from file
|
||||
value_type = 'none'
|
||||
with open(filename, 'r') as f:
|
||||
for line in f:
|
||||
line = line.strip().lower()
|
||||
if line.startswith("#") or line == "": # skip comments and empty lines
|
||||
continue
|
||||
value_type = line # first non-comment line is type
|
||||
break
|
||||
return value_type
|
||||
|
||||
def write_offline_debug_script(SCRIPTDIR):
|
||||
# prepare script for offline debugging from console
|
||||
# - only tested on linux
|
||||
import shutil
|
||||
ink_file = os.path.join(SCRIPTDIR, ".ink.sh")
|
||||
with open(ink_file, 'w') as f:
|
||||
f.write(f"#!/usr/bin/env bash\n\n")
|
||||
f.write(f"# version: {sys.version}\n") # python version
|
||||
|
||||
myargs = " ".join(sys.argv[1:])
|
||||
f.write(f'# script: {sys.argv[0]} arguments: {myargs}\n') # script name and arguments
|
||||
|
||||
# python module path
|
||||
f.write(f"# python sys.path:\n")
|
||||
for p in sys.path:
|
||||
f.write(f"# {p}\n")
|
||||
|
||||
# print PYTHONPATH one per line
|
||||
f.write(f"# PYTHONPATH:\n")
|
||||
for p in os.environ.get('PYTHONPATH', '').split(os.pathsep):
|
||||
f.write(f"# {p}\n")
|
||||
|
||||
# take argument that not start with '-' as file name
|
||||
svg_file = " ".join([arg for arg in sys.argv[1:] if not arg.startswith('-')])
|
||||
f.write(f"# copy {svg_file} to .ink.svg\n")
|
||||
# check if filer are not the same
|
||||
if svg_file != '.ink.svg':
|
||||
shutil.copy(svg_file, f'{SCRIPTDIR}/.ink.svg') # copy file to .ink.svg
|
||||
myargs = myargs.replace(svg_file, '.ink.svg') # replace file name with .ink.svg
|
||||
|
||||
# export INK*|PYTHON* environment variables
|
||||
for k, v in sorted(os.environ.items()):
|
||||
if k.startswith('INK') or k.startswith('PYTHON'):
|
||||
f.write(f'export {k}="{v}"\n')
|
||||
|
||||
# f.write(f"# python3 -m debugpy --listen 5678 --wait-for-client inkstitch.py {myargs}\n")
|
||||
f.write(f"python3 inkstitch.py {myargs}\n")
|
||||
os.chmod(ink_file, 0o0755) # make file executable
|
Ładowanie…
Reference in New Issue