traceflow-py 1.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
traceflow/__init__.py ADDED
@@ -0,0 +1,22 @@
1
+ """
2
+ TraceFlow — A lightweight Python decorator for visually tracing function execution.
3
+
4
+ Usage:
5
+ from traceflow import watch
6
+
7
+ @watch()
8
+ def my_function(x):
9
+ return x * 2
10
+
11
+ Global controls:
12
+ import traceflow
13
+ traceflow.disable()
14
+ traceflow.enable()
15
+ traceflow.is_enabled()
16
+ """
17
+
18
+ from traceflow.core import watch, enable, disable, is_enabled
19
+
20
+ __version__ = "1.2.0"
21
+ __author__ = "Aarav Agarwal"
22
+ __all__ = ["watch", "enable", "disable", "is_enabled"]
traceflow/core.py ADDED
@@ -0,0 +1,227 @@
1
+ """
2
+ Core implementation of the TraceFlow @watch decorator.
3
+
4
+ This module contains all tracing logic: call tree rendering, timing,
5
+ variable state tracking, truncation, exception handling, and async support.
6
+ """
7
+
8
+ import functools
9
+ import contextvars
10
+ import inspect
11
+ import time as _time
12
+ import sys as _sys
13
+
14
+ # ── Global State ────────────────────────────────────────────────
15
+
16
+ _depth = contextvars.ContextVar('depth', default=0)
17
+ _enabled = True
18
+
19
+
20
+ def enable():
21
+ """Enable all tracing globally."""
22
+ global _enabled
23
+ _enabled = True
24
+
25
+
26
+ def disable():
27
+ """Disable all tracing globally. Decorated functions still execute normally."""
28
+ global _enabled
29
+ _enabled = False
30
+
31
+
32
+ def is_enabled():
33
+ """Return whether tracing is currently enabled."""
34
+ return _enabled
35
+
36
+
37
+ # ── Decorator ───────────────────────────────────────────────────
38
+
39
+ def watch(func=None, *, track_time=True, truncate_len=50, max_depth=None, export_path=None, track_vars=False):
40
+ """
41
+ Decorator that traces function calls, producing an indented call tree.
42
+
43
+ Args:
44
+ track_time (bool): Append execution time to each return line. Default True.
45
+ truncate_len (int): Max characters for argument/return repr. 0 or None to disable.
46
+ max_depth (int): Stop tracing beyond this call depth. None for unlimited.
47
+ export_path (str): Write trace output to this file instead of stdout.
48
+ track_vars (bool): Log local variable assignments inside the function (sync only).
49
+
50
+ Returns:
51
+ The decorated function (sync or async wrapper).
52
+
53
+ Example::
54
+
55
+ @watch(track_time=True)
56
+ def fibonacci(n):
57
+ if n <= 1:
58
+ return n
59
+ return fibonacci(n - 1) + fibonacci(n - 2)
60
+
61
+ fibonacci(3)
62
+ # fibonacci(3)
63
+ # ├── fibonacci(2)
64
+ # │ ├── fibonacci(1)
65
+ # │ │ └── return 1 [0.0003s]
66
+ # │ ├── fibonacci(0)
67
+ # │ │ └── return 0 [0.0000s]
68
+ # │ └── return 1 [0.0006s]
69
+ # ├── fibonacci(1)
70
+ # │ └── return 1 [0.0000s]
71
+ # └── return 2 [0.0008s]
72
+ """
73
+ if func is None:
74
+ return functools.partial(watch, track_time=track_time, truncate_len=truncate_len,
75
+ max_depth=max_depth, export_path=export_path, track_vars=track_vars)
76
+
77
+ # ── Helpers ─────────────────────────────────────────────────
78
+
79
+ def _get_repr(val):
80
+ """Return a repr string for *val*, truncated to *truncate_len* characters."""
81
+ s = repr(val)
82
+ if truncate_len and len(s) > truncate_len:
83
+ s = s[:truncate_len - 3] + "..."
84
+ return s
85
+
86
+ def _log(msg):
87
+ """Print to console or append to file."""
88
+ if export_path:
89
+ try:
90
+ with open(export_path, "a", encoding="utf-8") as f:
91
+ f.write(msg + "\n")
92
+ except Exception:
93
+ pass
94
+ else:
95
+ print(msg)
96
+
97
+ def _prefix(depth):
98
+ """Build the tree prefix for a child node at the given depth."""
99
+ if depth == 0:
100
+ return ""
101
+ return "│ " * (depth - 1) + "├── "
102
+
103
+ def _continuation(depth):
104
+ """Build the continuation (vertical lines) for content at the given depth."""
105
+ return "│ " * depth
106
+
107
+ # ── Trace entry / exit ──────────────────────────────────────
108
+
109
+ def _trace_call(args, kwargs, depth):
110
+ """Print the function call line and return start_time."""
111
+ pos_args = [_get_repr(a) for a in args]
112
+ kw_args = [f"{k}={_get_repr(v)}" for k, v in kwargs.items()]
113
+ all_args = ", ".join(pos_args + kw_args)
114
+ call_str = f"{func.__name__}({all_args})"
115
+ _log(f"{_prefix(depth)}{call_str}")
116
+ return _time.perf_counter() if track_time else None
117
+
118
+ def _trace_return(depth, start_time, result=None, exc=None):
119
+ """Print the return/exception line as the last child of the call."""
120
+ elapsed = f" [{_time.perf_counter() - start_time:.4f}s]" if (track_time and start_time) else ""
121
+ ret_prefix = _continuation(depth) + "└── "
122
+ if exc is not None:
123
+ _log(f"{ret_prefix}{type(exc).__name__}: {str(exc)}{elapsed}")
124
+ else:
125
+ _log(f"{ret_prefix}return {_get_repr(result)}{elapsed}")
126
+
127
+ # ── Variable state tracking (sys.settrace) ──────────────────
128
+
129
+ def _make_var_tracer(depth):
130
+ """Create a sys.settrace local tracer for variable state tracking."""
131
+ var_prefix = _continuation(depth) + "│ "
132
+ prev_locals = [None] # None = first snapshot not yet captured
133
+ target_code = func.__code__
134
+
135
+ def local_tracer(frame, event, arg):
136
+ if event == 'line':
137
+ current = {}
138
+ for k, v in frame.f_locals.items():
139
+ if k.startswith('_'):
140
+ continue
141
+ try:
142
+ current[k] = _get_repr(v)
143
+ except Exception:
144
+ current[k] = '<repr error>'
145
+
146
+ if prev_locals[0] is None:
147
+ # First line event — capture parameters as baseline, don't log them
148
+ prev_locals[0] = current
149
+ else:
150
+ for k, v in current.items():
151
+ if k not in prev_locals[0] or prev_locals[0][k] != v:
152
+ _log(f"{var_prefix}· {k} = {v}")
153
+ prev_locals[0] = current
154
+ return local_tracer
155
+
156
+ def global_tracer(frame, event, arg):
157
+ if event == 'call' and frame.f_code is target_code:
158
+ return local_tracer
159
+ return None
160
+
161
+ return global_tracer
162
+
163
+ # ── Wrapper selection ───────────────────────────────────────
164
+
165
+ if inspect.iscoroutinefunction(func):
166
+ @functools.wraps(func)
167
+ async def async_wrapper(*args, **kwargs):
168
+ if not _enabled:
169
+ return await func(*args, **kwargs)
170
+
171
+ depth = _depth.get()
172
+ if max_depth is not None and depth >= max_depth:
173
+ token = _depth.set(depth + 1)
174
+ try:
175
+ return await func(*args, **kwargs)
176
+ finally:
177
+ _depth.reset(token)
178
+
179
+ start_time = _trace_call(args, kwargs, depth)
180
+ token = _depth.set(depth + 1)
181
+ # track_vars not supported for async (sys.settrace is thread-level)
182
+ try:
183
+ result = await func(*args, **kwargs)
184
+ _depth.reset(token)
185
+ _trace_return(depth, start_time, result=result)
186
+ return result
187
+ except Exception as e:
188
+ _depth.reset(token)
189
+ _trace_return(depth, start_time, exc=e)
190
+ raise
191
+ return async_wrapper
192
+ else:
193
+ @functools.wraps(func)
194
+ def sync_wrapper(*args, **kwargs):
195
+ if not _enabled:
196
+ return func(*args, **kwargs)
197
+
198
+ depth = _depth.get()
199
+ if max_depth is not None and depth >= max_depth:
200
+ token = _depth.set(depth + 1)
201
+ try:
202
+ return func(*args, **kwargs)
203
+ finally:
204
+ _depth.reset(token)
205
+
206
+ start_time = _trace_call(args, kwargs, depth)
207
+ token = _depth.set(depth + 1)
208
+
209
+ if track_vars:
210
+ old_trace = _sys.gettrace()
211
+ _sys.settrace(_make_var_tracer(depth))
212
+
213
+ try:
214
+ result = func(*args, **kwargs)
215
+ except Exception as e:
216
+ if track_vars:
217
+ _sys.settrace(old_trace)
218
+ _depth.reset(token)
219
+ _trace_return(depth, start_time, exc=e)
220
+ raise
221
+ else:
222
+ if track_vars:
223
+ _sys.settrace(old_trace)
224
+ _depth.reset(token)
225
+ _trace_return(depth, start_time, result=result)
226
+ return result
227
+ return sync_wrapper
@@ -0,0 +1,409 @@
1
+ Metadata-Version: 2.4
2
+ Name: traceflow-py
3
+ Version: 1.2.0
4
+ Summary: A lightweight decorator for visually tracing Python function calls with indented call trees, timing, variable tracking, and async support.
5
+ Author: Aarav Agarwal
6
+ License: All Rights Reserved
7
+ Project-URL: Homepage, https://github.com/Firestar3/TraceFlow
8
+ Project-URL: Repository, https://github.com/Firestar3/TraceFlow
9
+ Project-URL: Bug Tracker, https://github.com/Firestar3/TraceFlow/issues
10
+ Keywords: tracing,debugging,decorator,call-tree,profiling,recursion,async,logging
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Topic :: Software Development :: Debuggers
14
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.7
17
+ Classifier: Programming Language :: Python :: 3.8
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Operating System :: OS Independent
24
+ Requires-Python: >=3.7
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Dynamic: license-file
28
+
29
+ # TraceFlow
30
+
31
+ **TraceFlow** is a lightweight, zero-dependency Python decorator library for visually tracing function execution. It renders beautiful, indented call trees directly to your console — making debugging recursive, nested, and async code effortless.
32
+
33
+ ```
34
+ fibonacci(3)
35
+ ├── fibonacci(2)
36
+ │ ├── fibonacci(1)
37
+ │ │ └── return 1 [0.0003s]
38
+ │ ├── fibonacci(0)
39
+ │ │ └── return 0 [0.0000s]
40
+ │ └── return 1 [0.0006s]
41
+ ├── fibonacci(1)
42
+ │ └── return 1 [0.0000s]
43
+ └── return 2 [0.0008s]
44
+ ```
45
+
46
+ ---
47
+
48
+ ## Features
49
+
50
+ | Feature | Description |
51
+ |---|---|
52
+ | 🌳 **Call Trees** | Nested, indented tree visualization for every function call |
53
+ | ⏱️ **Execution Time** | Per-call timing with `[0.0042s]` annotations |
54
+ | 📐 **Truncation** | Smart character-limit truncation for large arguments and returns |
55
+ | 💥 **Exception Tracing** | Captures and renders exceptions inline in the call tree |
56
+ | 🔁 **Async Support** | Safely traces `async`/`await` functions and `asyncio.gather` |
57
+ | 🔒 **Depth Limiting** | Cap tracing depth with `max_depth` to reduce noise |
58
+ | 📁 **File Export** | Redirect trace output to a file instead of the console |
59
+ | 🔇 **Global Toggle** | `traceflow.disable()` / `traceflow.enable()` to control all tracing at runtime |
60
+ | 🔬 **Variable Tracking** | `track_vars=True` to log every local variable assignment inside a function |
61
+
62
+ ---
63
+
64
+ ## Installation
65
+
66
+ ```bash
67
+ # Clone the repo
68
+ git clone https://github.com/Firestar3/TraceFlow.git
69
+ cd TraceFlow
70
+
71
+ # Install locally
72
+ pip install .
73
+ ```
74
+
75
+ Or simply drop the `traceflow/` folder into your project — no dependencies required.
76
+
77
+ ---
78
+
79
+ ## Quick Start
80
+
81
+ ```python
82
+ from traceflow import watch
83
+
84
+ @watch()
85
+ def fibonacci(n):
86
+ if n <= 1:
87
+ return n
88
+ return fibonacci(n - 1) + fibonacci(n - 2)
89
+
90
+ fibonacci(3)
91
+ ```
92
+
93
+ **Output:**
94
+ ```
95
+ fibonacci(3)
96
+ ├── fibonacci(2)
97
+ │ ├── fibonacci(1)
98
+ │ │ └── return 1 [0.0003s]
99
+ │ ├── fibonacci(0)
100
+ │ │ └── return 0 [0.0000s]
101
+ │ └── return 1 [0.0006s]
102
+ ├── fibonacci(1)
103
+ │ └── return 1 [0.0000s]
104
+ └── return 2 [0.0008s]
105
+ ```
106
+
107
+ ---
108
+
109
+ ## Feature Guide
110
+
111
+ ### 1. Execution Time Tracking
112
+
113
+ Every traced call automatically measures wall-clock time.
114
+
115
+ ```python
116
+ from traceflow import watch
117
+ import time
118
+
119
+ @watch(track_time=True)
120
+ def slow_add(a, b):
121
+ time.sleep(0.05)
122
+ return a + b
123
+
124
+ slow_add(10, 20)
125
+ ```
126
+
127
+ ```
128
+ slow_add(10, 20)
129
+ └── return 30 [0.0502s]
130
+ ```
131
+
132
+ Set `track_time=False` to disable timing:
133
+
134
+ ```python
135
+ @watch(track_time=False)
136
+ def add(a, b):
137
+ return a + b
138
+
139
+ add(1, 2)
140
+ ```
141
+
142
+ ```
143
+ add(1, 2)
144
+ └── return 3
145
+ ```
146
+
147
+ ---
148
+
149
+ ### 2. Argument & Return Truncation
150
+
151
+ Prevent massive data structures from flooding your console with `truncate_len`.
152
+
153
+ ```python
154
+ @watch(truncate_len=30)
155
+ def process(data):
156
+ return [x * 2 for x in data]
157
+
158
+ process(list(range(50)))
159
+ ```
160
+
161
+ ```
162
+ process([0, 1, 2, 3, 4, 5, 6, 7, 8,...)
163
+ └── return [0, 2, 4, 6, 8, 10, 12, 14,... [0.0000s]
164
+ ```
165
+
166
+ The character limit applies to both arguments and return values. Set `truncate_len=None` or `truncate_len=0` to disable truncation entirely.
167
+
168
+ ---
169
+
170
+ ### 3. Exception Tracing
171
+
172
+ Exceptions are captured, rendered in the tree, and re-raised — so your error handling works normally while you get full visibility.
173
+
174
+ ```python
175
+ @watch()
176
+ def divide(a, b):
177
+ return a / b
178
+
179
+ @watch()
180
+ def safe_math(x, y):
181
+ return divide(x, y)
182
+
183
+ try:
184
+ safe_math(10, 0)
185
+ except ZeroDivisionError:
186
+ pass
187
+ ```
188
+
189
+ ```
190
+ safe_math(10, 0)
191
+ ├── divide(10, 0)
192
+ │ └── ZeroDivisionError: division by zero [0.0000s]
193
+ └── ZeroDivisionError: division by zero [0.0000s]
194
+ ```
195
+
196
+ The exception propagates through each level of the call tree, showing exactly where it originated and how it bubbled up.
197
+
198
+ ---
199
+
200
+ ### 4. Max Depth Limiting
201
+
202
+ Reduce noise in deeply recursive functions by capping the trace depth.
203
+
204
+ ```python
205
+ @watch(max_depth=2)
206
+ def deep_fib(n):
207
+ if n <= 1:
208
+ return n
209
+ return deep_fib(n - 1) + deep_fib(n - 2)
210
+
211
+ deep_fib(4)
212
+ ```
213
+
214
+ ```
215
+ deep_fib(4)
216
+ ├── deep_fib(3)
217
+ │ └── return 2 [0.0000s]
218
+ ├── deep_fib(2)
219
+ │ └── return 1 [0.0000s]
220
+ └── return 3 [0.0000s]
221
+ ```
222
+
223
+ Calls beyond `max_depth` still execute normally — they just aren't traced.
224
+
225
+ ---
226
+
227
+ ### 5. Async Function Support
228
+
229
+ TraceFlow natively supports `async` functions. Concurrent tasks from `asyncio.gather` are traced cleanly.
230
+
231
+ ```python
232
+ import asyncio
233
+ from traceflow import watch
234
+
235
+ @watch()
236
+ async def fetch_data(id):
237
+ await asyncio.sleep(0.01)
238
+ return f"data_{id}"
239
+
240
+ @watch()
241
+ async def fetch_all():
242
+ return await asyncio.gather(fetch_data(1), fetch_data(2), fetch_data(3))
243
+
244
+ asyncio.run(fetch_all())
245
+ ```
246
+
247
+ ```
248
+ fetch_all()
249
+ ├── fetch_data(1)
250
+ │ └── return 'data_1' [0.0123s]
251
+ ├── fetch_data(2)
252
+ │ └── return 'data_2' [0.0123s]
253
+ ├── fetch_data(3)
254
+ │ └── return 'data_3' [0.0123s]
255
+ └── return ['data_1', 'data_2', 'data_3'] [0.0126s]
256
+ ```
257
+
258
+ ---
259
+
260
+ ### 6. File Export
261
+
262
+ Redirect all trace output to a text file instead of the console.
263
+
264
+ ```python
265
+ @watch(export_path="trace_output.txt")
266
+ def fibonacci(n):
267
+ if n <= 1:
268
+ return n
269
+ return fibonacci(n - 1) + fibonacci(n - 2)
270
+
271
+ fibonacci(3)
272
+ # -> Trace written to trace_output.txt
273
+ ```
274
+
275
+ The file is appended to, so multiple runs accumulate in the same file.
276
+
277
+ ---
278
+
279
+ ### 7. Global Enable / Disable
280
+
281
+ Turn all tracing on or off at runtime without removing decorators. Functions still execute normally when tracing is disabled — only the output is suppressed.
282
+
283
+ ```python
284
+ import traceflow
285
+ from traceflow import watch
286
+
287
+ @watch()
288
+ def add(a, b):
289
+ return a + b
290
+
291
+ add(1, 2) # Trace is printed
292
+
293
+ traceflow.disable()
294
+ add(3, 4) # No trace output, but returns 7 normally
295
+
296
+ traceflow.enable()
297
+ add(5, 6) # Trace resumes
298
+ ```
299
+
300
+ ```
301
+ add(1, 2)
302
+ └── return 3 [0.0000s]
303
+
304
+ add(5, 6)
305
+ └── return 11 [0.0000s]
306
+ ```
307
+
308
+ Also available: `traceflow.is_enabled()` to check current state.
309
+
310
+ ---
311
+
312
+ ### 8. Variable State Tracking
313
+
314
+ See exactly how local variables change inside a function, line by line. Parameters are captured as a baseline and not logged (they're already visible in the call signature).
315
+
316
+ ```python
317
+ @watch(track_vars=True)
318
+ def compute(x, y):
319
+ total = x + y
320
+ doubled = total * 2
321
+ message = f"Result: {doubled}"
322
+ return doubled
323
+
324
+ compute(5, 3)
325
+ ```
326
+
327
+ ```
328
+ compute(5, 3)
329
+ │ · total = 8
330
+ │ · doubled = 16
331
+ │ · message = 'Result: 16'
332
+ └── return 16 [0.0002s]
333
+ ```
334
+
335
+ Variable tracking inside a loop:
336
+
337
+ ```python
338
+ @watch(track_vars=True)
339
+ def sum_list(items):
340
+ total = 0
341
+ for val in items:
342
+ total += val
343
+ return total
344
+
345
+ sum_list([10, 20, 30])
346
+ ```
347
+
348
+ ```
349
+ sum_list([10, 20, 30])
350
+ │ · total = 0
351
+ │ · val = 10
352
+ │ · total = 10
353
+ │ · val = 20
354
+ │ · total = 30
355
+ │ · val = 30
356
+ │ · total = 60
357
+ └── return 60 [0.0001s]
358
+ ```
359
+
360
+ > **Note:** Variable tracking uses `sys.settrace` and is only available for synchronous functions. It adds overhead and is best used for targeted debugging, not production code.
361
+
362
+ ---
363
+
364
+ ### 9. Keyword Arguments
365
+
366
+ TraceFlow displays both positional and keyword arguments.
367
+
368
+ ```python
369
+ @watch()
370
+ def greet(name, greeting="Hello", punctuation="!"):
371
+ return f"{greeting}, {name}{punctuation}"
372
+
373
+ greet("Aarav", greeting="Hey", punctuation="!!")
374
+ ```
375
+
376
+ ```
377
+ greet('Aarav', greeting='Hey', punctuation='!!')
378
+ └── return 'Hey, Aarav!!' [0.0000s]
379
+ ```
380
+
381
+ ---
382
+
383
+ ## Configuration Reference
384
+
385
+ | Parameter | Type | Default | Description |
386
+ |---|---|---|---|
387
+ | `track_time` | `bool` | `True` | Append `[0.0042s]` execution time to each return line |
388
+ | `truncate_len` | `int` | `50` | Max characters for argument/return repr. `0` or `None` to disable |
389
+ | `max_depth` | `int` | `None` | Stop tracing beyond this call depth. `None` for unlimited |
390
+ | `export_path` | `str` | `None` | Write trace to a file instead of stdout |
391
+ | `track_vars` | `bool` | `False` | Log local variable assignments inside the function (sync only) |
392
+
393
+ ### Global Functions
394
+
395
+ | Function | Description |
396
+ |---|---|
397
+ | `traceflow.enable()` | Enable all tracing (default state) |
398
+ | `traceflow.disable()` | Disable all tracing — decorated functions still run normally |
399
+ | `traceflow.is_enabled()` | Returns `True` if tracing is currently active |
400
+
401
+ ---
402
+
403
+ ## Author
404
+
405
+ **Aarav Agarwal**
406
+
407
+ ## License
408
+
409
+ All Rights Reserved.
@@ -0,0 +1,7 @@
1
+ traceflow/__init__.py,sha256=rdrpNy3Ha-bXsK77HX3BfKYX-puf24PR5cnUrBrRtD4,476
2
+ traceflow/core.py,sha256=yttjGSbLDl_NPcXNuN651ePfmVM2G-57oOqExPSaMrM,8546
3
+ traceflow_py-1.2.0.dist-info/licenses/LICENSE,sha256=XD4Y9VNyR0ZiCXGbFwyV0JmVnsxGb6m88KVOyK-Ah-c,470
4
+ traceflow_py-1.2.0.dist-info/METADATA,sha256=njO7m0_g8YQVSm-yj-x7P_S4oLJ8ie7ifPGwlPEAVtM,10053
5
+ traceflow_py-1.2.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
6
+ traceflow_py-1.2.0.dist-info/top_level.txt,sha256=SqOQaaNe15Fx2bMjjT4p-KNgKl2MqxAQVJT5ocAXjZo,10
7
+ traceflow_py-1.2.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,11 @@
1
+ All Rights Reserved
2
+
3
+ Copyright (c) 2025 Aarav Agarwal
4
+
5
+ This software and associated documentation files (the "Software") may not be
6
+ copied, modified, merged, published, distributed, sublicensed, or sold without
7
+ the express written permission of the copyright holder.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
10
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
11
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
@@ -0,0 +1 @@
1
+ traceflow