summaryrefslogtreecommitdiff
path: root/lib/debug.py
blob: 0d6af104b8bb032774306ca1daf7a74af19e9a46 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# 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 atexit
import os
import socket
import sys
import time
from contextlib import contextmanager
from datetime import datetime

import inkex
from lxml import etree

from .svg import line_strings_to_path
from .svg.tags import INKSCAPE_GROUPMODE, INKSCAPE_LABEL


def check_enabled(func):
    def decorated(self, *args, **kwargs):
        if self.enabled:
            func(self, *args, **kwargs)

    return decorated


class Debug(object):
    def __init__(self):
        self.enabled = False
        self.last_log_time = None
        self.current_layer = None
        self.group_stack = []

    def enable(self):
        self.enabled = True
        self.init_log()
        self.init_debugger()
        self.init_svg()

    def init_log(self):
        self.log_file = os.path.join(os.path.dirname(os.path.dirname(__file__)), "debug.log")
        # delete old content
        with open(self.log_file, "w"):
            pass
        self.log("Debug logging enabled.")

    def init_debugger(self):
        # How to debug Ink/Stitch with LiClipse:
        #
        # 1. Install LiClipse (liclipse.com) -- no need to install Eclipse first
        # 2. Start debug server as described here: http://www.pydev.org/manual_adv_remote_debugger.html
        #    * follow the "Note:" to enable the debug server menu item
        # 3. Create a file named "DEBUG" next to inkstitch.py in your git clone.
        # 4. Run any extension and PyDev will start debugging.

        ###

        # To debug with PyCharm:

        # You must use the PyCharm Professional Edition and _not_ the Community
        # Edition. Jetbrains has chosen to make remote debugging a Pro feature.
        # To debug Inkscape python extensions, the extension code and Inkscape run
        # independently of PyCharm, and only communicate with the debugger via a
        # TCP socket. Thus, debugging is "remote," even if it's on the same machine,
        # connected via the loopback interface.
        #
        # 1.     pip install pydev_pycharm
        #
        #    pydev_pycharm is versioned frequently. Jetbrains suggests installing
        #    a version at least compatible with the current build. For example, if your
        #    PyCharm build, as found in menu PyCharm -> About Pycharm is 223.8617.48,
        #    you could do:
        #        pip install pydevd-pycharm~=223.8617.48
        #
        # 2. From the Pycharm "Run" menu, choose "Edit Configurations..." and create a new
        #    configuration. Set "IDE host name:" to  "localhost" and "Port:" to 5678.
        #    You can leave the default settings for all other choices.
        #
        # 3. Touch a file named "DEBUG" at the top of your git repo, as above.
        #
        # 4. Create a symbolic link in the Inkscape extensions directory to the
        #    top-level directory of your git repo. On a mac, for example:
        #        cd ~/Library/Application\ Support/org.inkscape.Inkscape/config/inkscape/extensions/
        #        ln -s <full path to the top level of your Ink/Stitch git repo>
        #    On other architectures it may be:
        #        cd ~/.config/inkscape/extensions
        #        ln -s <full path to the top level of your Ink/Stitch git repo>
        #    Remove any other Ink/Stitch files or references to Ink/Stitch from the
        #    extensions directory, or you'll see duplicate entries in the Ink/Stitch
        #    extensions menu in Inkscape.
        #
        # 5. In the execution env for Inkscape, set the environment variable
        #    PYCHARM_REMOTE_DEBUG to any value, and launch Inkscape. If you're starting
        #    Inkscape from the PyCharm Terminal pane, you can do:
        #        export PYCHARM_REMOTE_DEBUG=true;inkscape
        #
        # 6. In Pycharm, either click on the green "bug" icon if visible in the upper
        #    right or press Ctrl-D to start debugging.The PyCharm debugger pane will
        #    display the message "Waiting for process connection..."
        #
        # 7. Do some action in Inkscape which invokes Ink/Stitch extension code, and the
        #    debugger will be triggered. If you've left "Suspend after connect" checked
        #    in the Run configuration, PyCharm will pause in the "self.log("Enabled
        #    PyDev debugger.)" statement, below. Uncheck the box to have it continue
        #    automatically to your first set breakpoint.

        try:
            if 'PYCHARM_REMOTE_DEBUG' in os.environ:
                import pydevd_pycharm
            else:
                import pydevd

        except ImportError:
            self.log("importing pydevd failed (debugger disabled)")

        # pydevd likes to shout about errors to stderr whether I want it to or not
        with open(os.devnull, 'w') as devnull:
            stderr = sys.stderr
            sys.stderr = devnull

            try:
                if 'PYCHARM_REMOTE_DEBUG' in os.environ:
                    pydevd_pycharm.settrace('localhost', port=5678, stdoutToServer=True,
                                            stderrToServer=True)
                else:
                    pydevd.settrace()

            except socket.error as error:
                self.log("Debugging: connection to pydevd failed: %s", error)
                self.log("Be sure to run 'Start debugging server' in PyDev to enable debugging.")
            else:
                self.log("Enabled PyDev debugger.")

            sys.stderr = stderr

    def init_svg(self):
        self.svg = etree.Element("svg", nsmap=inkex.NSS)
        atexit.register(self.save_svg)

    def save_svg(self):
        tree = etree.ElementTree(self.svg)
        debug_svg = os.path.join(os.path.dirname(os.path.dirname(__file__)), "debug.svg")
        tree.write(debug_svg)

    @check_enabled
    def add_layer(self, name="Debug"):
        layer = etree.Element("g", {
            INKSCAPE_GROUPMODE: "layer",
            INKSCAPE_LABEL: name,
            "style": "display: none"
        })
        self.svg.append(layer)
        self.current_layer = layer

    @check_enabled
    def open_group(self, name="Group"):
        group = etree.Element("g", {
            INKSCAPE_LABEL: name
        })

        self.log_svg_element(group)
        self.group_stack.append(group)

    @check_enabled
    def close_group(self):
        if self.group_stack:
            self.group_stack.pop()

    @check_enabled
    def log(self, message, *args):
        if self.last_log_time:
            message = "(+%s) %s" % (datetime.now() - self.last_log_time, message)

        self.raw_log(message, *args)

    def raw_log(self, message, *args):
        now = datetime.now()
        timestamp = now.isoformat()
        self.last_log_time = now

        with open(self.log_file, "a") as logfile:
            print(timestamp, message % args, file=logfile)
            logfile.flush()

    def time(self, func):
        def decorated(*args, **kwargs):
            if self.enabled:
                self.raw_log("entering %s()", func.__name__)
                start = time.time()

            result = func(*args, **kwargs)

            if self.enabled:
                end = time.time()
                self.raw_log("leaving %s(), duration = %s", func.__name__, round(end - start, 6))

            return result

        return decorated

    @check_enabled
    def log_svg_element(self, element):
        if self.current_layer is None:
            self.add_layer()

        if self.group_stack:
            self.group_stack[-1].append(element)
        else:
            self.current_layer.append(element)

    @check_enabled
    def log_line_string(self, line_string, name=None, color=None):
        """Add a Shapely LineString to the SVG log."""
        self.log_line_strings([line_string], name, color)

    @check_enabled
    def log_line_strings(self, line_strings, name=None, color=None):
        path = line_strings_to_path(line_strings)
        path.set('style', str(inkex.Style({"stroke": color or "#000000", "stroke-width": "0.3", "fill": None})))

        if name is not None:
            path.set(INKSCAPE_LABEL, name)

        self.log_svg_element(path)

    @check_enabled
    def log_line(self, start, end, name="line", color=None):
        self.log_svg_element(etree.Element("path", {
            "d": "M%s,%s %s,%s" % (start + end),
            "style": str(inkex.Style({"stroke": color or "#000000", "stroke-width": "0.3", "fill": None})),
            INKSCAPE_LABEL: name
        }))

    @check_enabled
    def log_graph(self, graph, name="Graph", color=None):
        d = ""

        for edge in graph.edges:
            d += "M%s,%s %s,%s" % (edge[0] + edge[1])

        self.log_svg_element(etree.Element("path", {
            "d": d,
            "style": str(inkex.Style({"stroke": color or "#000000", "stroke-width": "0.3", "fill": None})),
            INKSCAPE_LABEL: name
        }))

    @contextmanager
    def time_this(self, label="code block"):
        if self.enabled:
            start = time.time()
            self.raw_log("begin %s", label)

        yield

        if self.enabled:
            self.raw_log("completed %s, duration = %s", label, time.time() - start)


debug = Debug()


def enable():
    debug.enable()