pyDiffTools 0.1.27__tar.gz → 0.1.28__tar.gz
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.
- {pydifftools-0.1.27/pyDiffTools.egg-info → pydifftools-0.1.28}/PKG-INFO +1 -1
- {pydifftools-0.1.27 → pydifftools-0.1.28/pyDiffTools.egg-info}/PKG-INFO +1 -1
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pydifftools/flowchart/graph.py +133 -36
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pydifftools/flowchart/watch_graph.py +59 -48
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pyproject.toml +1 -1
- {pydifftools-0.1.27 → pydifftools-0.1.28}/LICENSE.md +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/MANIFEST.in +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/README.rst +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/_quarto.yml +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/.jupyter_cache/__version__.txt +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/.jupyter_cache/executed/1a724af72b16f5a9e607e12b1c721645/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/.jupyter_cache/executed/1b28fc9daac9081847e5161b2c546f8a/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/.jupyter_cache/executed/231f64eee282fa225d1104935cf80a24/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/.jupyter_cache/executed/26e56f6b0ff54851a45145157f2f0dc4/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/.jupyter_cache/executed/311fabd7029ffd050d056e2f316eb50f/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/.jupyter_cache/executed/57da2021e5b156ac3adf01398201c723/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/.jupyter_cache/executed/62b24ea7da75011d92b0f8924faa208d/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/.jupyter_cache/executed/7f1b20d69d889514ab5d1cc92e3cb14f/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/.jupyter_cache/executed/86f74c8c54a87ff892d9b15dd714e8f0/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/.jupyter_cache/executed/88893b4234eac2945d9d6cb2e277f186/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/.jupyter_cache/executed/9a40046ada6f582ee34af00fbdbfb417/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/.jupyter_cache/executed/a1bf4d270d0641ff41faf1d7cce3439a/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/.jupyter_cache/executed/a3789f7d9585a781f2a1c60ce95ff10d/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/.jupyter_cache/executed/be46ecba858d39ad5f0c46902ddf1c02/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/.jupyter_cache/executed/d0cbc57a12f2ccce710a5afd04cc05e7/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/.jupyter_cache/executed/d3e12d320b14228f701231ae32ddd7dd/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/.jupyter_cache/executed/eb434f61555438d020a6970a5dbf9ee8/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/.jupyter_cache/executed/f6b0e73aa7fa029134665d4dde57e096/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/.jupyter_cache/executed/f719a6e4ff09873cb0ffb06ec9d232f9/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/.jupyter_cache/executed/feeee244a7ce3d60e1a227eb604df823/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/.jupyter_cache/global.db +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/example.qmd +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/example.tex +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/index.qmd +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/subproject1/.jupyter_cache/__version__.txt +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/subproject1/.jupyter_cache/executed/ca90e4df5f4f0583df6554156a68dc7f/base.ipynb +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/subproject1/.jupyter_cache/global.db +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/subproject1/end_vs_he_sketch.jpg +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/subproject1/independent.qmd +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/subproject1/index.qmd +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/subproject1/tasks.qmd +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/subproject1/test_include.qmd +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/subproject1/tryforerror.qmd +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/test_include.qmd +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pyDiffTools.egg-info/SOURCES.txt +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pyDiffTools.egg-info/dependency_links.txt +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pyDiffTools.egg-info/entry_points.txt +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pyDiffTools.egg-info/requires.txt +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pyDiffTools.egg-info/top_level.txt +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pydifftools/__init__.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pydifftools/browser_lifecycle.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pydifftools/check_numbers.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pydifftools/command_line.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pydifftools/command_registry.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pydifftools/comment_functions.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pydifftools/comment_tags.lua +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pydifftools/comment_tags_margin.lua +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pydifftools/comment_tags_no_comments.lua +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pydifftools/comment_toggle.js +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pydifftools/comments.css +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pydifftools/continuous.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pydifftools/copy_files.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pydifftools/diff-doc.js +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pydifftools/doc_contents.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pydifftools/flowchart/__init__.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pydifftools/flowchart/dot_to_yaml.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pydifftools/git_gd.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pydifftools/git_gd_qt.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pydifftools/html_comments.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pydifftools/html_uncomments.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pydifftools/log_example.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pydifftools/match_spaces.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pydifftools/notebook/__init__.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pydifftools/notebook/fast_build.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pydifftools/notebook/tex_to_qmd.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pydifftools/onewordify.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pydifftools/onewordify_undo.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pydifftools/outline.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pydifftools/rearrange_tex.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pydifftools/searchacro.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pydifftools/separate_comments.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pydifftools/split_conflict.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pydifftools/unseparate_comments.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pydifftools/update_check.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pydifftools/wrap_sentences.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/pydifftools/xml2xlsx.vbs +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/setup.cfg +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/tests/test_browser_lifecycle.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/tests/test_command_line_help.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/tests/test_continuous_shutdown.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/tests/test_rrng.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/tests/test_tex_to_qmd.py +0 -0
- {pydifftools-0.1.27 → pydifftools-0.1.28}/tests/test_update_check.py +0 -0
|
@@ -321,8 +321,7 @@ def _node_text_with_due(node, depends_on_undated_parent=False):
|
|
|
321
321
|
|
|
322
322
|
# Completed tasks should always show their calendar date so the original
|
|
323
323
|
# deadline remains visible even if it was today or overdue when finished.
|
|
324
|
-
|
|
325
|
-
is_completed = "complete" in style_name
|
|
324
|
+
is_completed = node_is_completed(node)
|
|
326
325
|
# Replace the actual date with high-visibility notices when the deadline
|
|
327
326
|
# is today or overdue. These are rendered in a bold 12 pt font so they are
|
|
328
327
|
# immediately noticeable in the diagram. Completed tasks skip these
|
|
@@ -367,6 +366,68 @@ def _node_text_with_due(node, depends_on_undated_parent=False):
|
|
|
367
366
|
return formatted
|
|
368
367
|
|
|
369
368
|
|
|
369
|
+
def node_is_completed(node):
|
|
370
|
+
return "complete" in str(node.get("style", ""))
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def node_is_endpoint(node):
|
|
374
|
+
return "endpoint" in str(node.get("style", ""))
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def trace_ancestors(
|
|
378
|
+
data,
|
|
379
|
+
start_name,
|
|
380
|
+
stop_at=None,
|
|
381
|
+
include_stopped=False,
|
|
382
|
+
skip_over=None,
|
|
383
|
+
):
|
|
384
|
+
ancestors = []
|
|
385
|
+
nodes = data.get("nodes", {})
|
|
386
|
+
if start_name not in nodes:
|
|
387
|
+
return ancestors
|
|
388
|
+
ancestors_to_visit = list(nodes[start_name].get("parents", []))
|
|
389
|
+
already_seen = set([start_name])
|
|
390
|
+
while ancestors_to_visit:
|
|
391
|
+
ancestor = ancestors_to_visit.pop()
|
|
392
|
+
if ancestor in already_seen:
|
|
393
|
+
continue
|
|
394
|
+
already_seen.add(ancestor)
|
|
395
|
+
if ancestor not in nodes:
|
|
396
|
+
continue
|
|
397
|
+
ancestor_node = nodes[ancestor]
|
|
398
|
+
if stop_at is not None and stop_at(ancestor, ancestor_node):
|
|
399
|
+
if include_stopped:
|
|
400
|
+
ancestors.append(ancestor)
|
|
401
|
+
continue
|
|
402
|
+
if skip_over is not None and skip_over(ancestor, ancestor_node):
|
|
403
|
+
for parent in ancestor_node.get("parents", []):
|
|
404
|
+
ancestors_to_visit.append(parent)
|
|
405
|
+
continue
|
|
406
|
+
ancestors.append(ancestor)
|
|
407
|
+
for parent in ancestor_node.get("parents", []):
|
|
408
|
+
ancestors_to_visit.append(parent)
|
|
409
|
+
return ancestors
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def endpoint_projects(data):
|
|
413
|
+
endpoints = set()
|
|
414
|
+
for name, node_data in data.get("nodes", {}).items():
|
|
415
|
+
if node_is_endpoint(node_data):
|
|
416
|
+
endpoints.add(name)
|
|
417
|
+
|
|
418
|
+
projects = {}
|
|
419
|
+
for endpoint in sorted(endpoints):
|
|
420
|
+
projects[endpoint] = [endpoint]
|
|
421
|
+
projects[endpoint].extend(
|
|
422
|
+
trace_ancestors(
|
|
423
|
+
data,
|
|
424
|
+
endpoint,
|
|
425
|
+
stop_at=lambda name, _node: name in endpoints,
|
|
426
|
+
)
|
|
427
|
+
)
|
|
428
|
+
return projects
|
|
429
|
+
|
|
430
|
+
|
|
370
431
|
def _node_label(text, wrap_width=55):
|
|
371
432
|
if text is None:
|
|
372
433
|
return ""
|
|
@@ -415,6 +476,8 @@ def _append_node(
|
|
|
415
476
|
if node_has_due:
|
|
416
477
|
for parent in node.get("parents", []):
|
|
417
478
|
parent_node = data["nodes"].get(parent, {})
|
|
479
|
+
if node_is_completed(parent_node):
|
|
480
|
+
continue
|
|
418
481
|
parent_due = parent_node.get("due")
|
|
419
482
|
if parent_due is None or not str(parent_due).strip():
|
|
420
483
|
depends_on_undated_parent = True
|
|
@@ -586,6 +649,65 @@ def yaml_to_dot(data, wrap_width=55, order_by_date=False):
|
|
|
586
649
|
return "\n".join(lines)
|
|
587
650
|
|
|
588
651
|
|
|
652
|
+
def _filter_nodes_for_dot(
|
|
653
|
+
data, node_names, include_completed_endpoint_ancestors=False
|
|
654
|
+
):
|
|
655
|
+
nodes = data.get("nodes", {})
|
|
656
|
+
included = []
|
|
657
|
+
included_set = set()
|
|
658
|
+
|
|
659
|
+
def include_node(name):
|
|
660
|
+
if name not in included_set:
|
|
661
|
+
included.append(name)
|
|
662
|
+
included_set.add(name)
|
|
663
|
+
|
|
664
|
+
for name in node_names:
|
|
665
|
+
if name not in nodes:
|
|
666
|
+
continue
|
|
667
|
+
if node_is_completed(nodes[name]):
|
|
668
|
+
continue
|
|
669
|
+
include_node(name)
|
|
670
|
+
|
|
671
|
+
if include_completed_endpoint_ancestors:
|
|
672
|
+
for name in list(included):
|
|
673
|
+
completed_endpoint_ancestors = trace_ancestors(
|
|
674
|
+
data,
|
|
675
|
+
name,
|
|
676
|
+
stop_at=lambda _name, node: node_is_endpoint(node),
|
|
677
|
+
include_stopped=True,
|
|
678
|
+
skip_over=lambda _name, node: (
|
|
679
|
+
node_is_completed(node) and not node_is_endpoint(node)
|
|
680
|
+
),
|
|
681
|
+
)
|
|
682
|
+
for ancestor in completed_endpoint_ancestors:
|
|
683
|
+
if ancestor not in nodes:
|
|
684
|
+
continue
|
|
685
|
+
if node_is_completed(nodes[ancestor]) and node_is_endpoint(
|
|
686
|
+
nodes[ancestor]
|
|
687
|
+
):
|
|
688
|
+
include_node(ancestor)
|
|
689
|
+
|
|
690
|
+
data_for_dot = {"nodes": {}, "styles": {}}
|
|
691
|
+
if "styles" in data:
|
|
692
|
+
data_for_dot["styles"] = data["styles"]
|
|
693
|
+
for name in included:
|
|
694
|
+
data_for_dot["nodes"][name] = dict(nodes[name])
|
|
695
|
+
for name in data_for_dot["nodes"]:
|
|
696
|
+
if "children" in data_for_dot["nodes"][name]:
|
|
697
|
+
data_for_dot["nodes"][name]["children"] = [
|
|
698
|
+
child
|
|
699
|
+
for child in data_for_dot["nodes"][name]["children"]
|
|
700
|
+
if child in included_set
|
|
701
|
+
]
|
|
702
|
+
if "parents" in data_for_dot["nodes"][name]:
|
|
703
|
+
data_for_dot["nodes"][name]["parents"] = [
|
|
704
|
+
parent
|
|
705
|
+
for parent in data_for_dot["nodes"][name]["parents"]
|
|
706
|
+
if parent in included_set
|
|
707
|
+
]
|
|
708
|
+
return data_for_dot
|
|
709
|
+
|
|
710
|
+
|
|
589
711
|
def save_graph_yaml(path, data):
|
|
590
712
|
# Ensure stored dates are normalized before writing.
|
|
591
713
|
_normalize_graph_dates(data)
|
|
@@ -610,6 +732,7 @@ def write_dot_from_yaml(
|
|
|
610
732
|
old_data=None,
|
|
611
733
|
validate_due_dates=False,
|
|
612
734
|
filter_task=None,
|
|
735
|
+
filter_completed=False,
|
|
613
736
|
resolve_due_date_conflict=None,
|
|
614
737
|
):
|
|
615
738
|
data = load_graph_yaml(str(yaml_path), old_data=old_data)
|
|
@@ -755,40 +878,14 @@ def write_dot_from_yaml(
|
|
|
755
878
|
)
|
|
756
879
|
# Include the target task alongside its ancestors in the filtered view.
|
|
757
880
|
ancestors = set([filter_task])
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
parents_to_check.append(grandparent)
|
|
767
|
-
incomplete_ancestors = set()
|
|
768
|
-
for name in ancestors:
|
|
769
|
-
if name not in data["nodes"]:
|
|
770
|
-
continue
|
|
771
|
-
if "complete" in str(data["nodes"][name].get("style", "")):
|
|
772
|
-
continue
|
|
773
|
-
incomplete_ancestors.add(name)
|
|
774
|
-
data_for_dot = {"nodes": {}, "styles": {}}
|
|
775
|
-
if "styles" in data:
|
|
776
|
-
data_for_dot["styles"] = data["styles"]
|
|
777
|
-
for name in incomplete_ancestors:
|
|
778
|
-
data_for_dot["nodes"][name] = dict(data["nodes"][name])
|
|
779
|
-
for name in data_for_dot["nodes"]:
|
|
780
|
-
if "children" in data_for_dot["nodes"][name]:
|
|
781
|
-
data_for_dot["nodes"][name]["children"] = [
|
|
782
|
-
child
|
|
783
|
-
for child in data_for_dot["nodes"][name]["children"]
|
|
784
|
-
if child in incomplete_ancestors
|
|
785
|
-
]
|
|
786
|
-
if "parents" in data_for_dot["nodes"][name]:
|
|
787
|
-
data_for_dot["nodes"][name]["parents"] = [
|
|
788
|
-
parent
|
|
789
|
-
for parent in data_for_dot["nodes"][name]["parents"]
|
|
790
|
-
if parent in incomplete_ancestors
|
|
791
|
-
]
|
|
881
|
+
ancestors.update(trace_ancestors(data, filter_task))
|
|
882
|
+
data_for_dot = _filter_nodes_for_dot(data, ancestors)
|
|
883
|
+
elif filter_completed:
|
|
884
|
+
data_for_dot = _filter_nodes_for_dot(
|
|
885
|
+
data,
|
|
886
|
+
data.get("nodes", {}).keys(),
|
|
887
|
+
include_completed_endpoint_ancestors=True,
|
|
888
|
+
)
|
|
792
889
|
dot_str = yaml_to_dot(
|
|
793
890
|
data_for_dot, wrap_width=wrap_width, order_by_date=order_by_date
|
|
794
891
|
)
|
|
@@ -26,7 +26,7 @@ from pydifftools.browser_lifecycle import (
|
|
|
26
26
|
browser_window_is_alive,
|
|
27
27
|
close_browser_window,
|
|
28
28
|
)
|
|
29
|
-
from .graph import EmptyGraphYamlError, write_dot_from_yaml
|
|
29
|
+
from .graph import EmptyGraphYamlError, endpoint_projects, write_dot_from_yaml
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
def _reload_svg(driver, svg_src) -> None:
|
|
@@ -236,35 +236,48 @@ def _watch_view_state_from_params(params):
|
|
|
236
236
|
# reliably switch back to the overview without depending on prior state.
|
|
237
237
|
order_by_date = False
|
|
238
238
|
target_task = None
|
|
239
|
+
filter_completed = False
|
|
240
|
+
if "p" in params:
|
|
241
|
+
p_value = params["p"][-1]
|
|
242
|
+
filter_completed = p_value in ("1", "true", "yes", "on", "")
|
|
239
243
|
if "d" in params:
|
|
240
244
|
d_value = params["d"][-1]
|
|
241
245
|
order_by_date = d_value in ("1", "true", "yes", "on", "")
|
|
246
|
+
if order_by_date:
|
|
247
|
+
filter_completed = False
|
|
242
248
|
if "t" in params:
|
|
243
249
|
t_value = params["t"][-1]
|
|
244
250
|
if t_value is not None and str(t_value) != "":
|
|
245
251
|
target_task = str(t_value)
|
|
246
252
|
order_by_date = False
|
|
247
|
-
|
|
253
|
+
filter_completed = False
|
|
254
|
+
return order_by_date, target_task, filter_completed
|
|
248
255
|
|
|
249
256
|
|
|
250
|
-
def _watch_footer_links(
|
|
257
|
+
def _watch_footer_links(
|
|
258
|
+
order_by_date, target_task=None, filter_completed=False
|
|
259
|
+
):
|
|
251
260
|
links = []
|
|
252
|
-
if order_by_date:
|
|
253
|
-
links.append(("/", "project overview"))
|
|
254
|
-
else:
|
|
261
|
+
if not order_by_date:
|
|
255
262
|
links.append(("/?d=1", "date-ordered"))
|
|
256
|
-
|
|
257
|
-
|
|
263
|
+
if not filter_completed:
|
|
264
|
+
links.append(("/?p=1", "full plan"))
|
|
265
|
+
if order_by_date or filter_completed:
|
|
266
|
+
links.append(("/", "project overview"))
|
|
267
|
+
elif target_task is not None and str(target_task).strip():
|
|
268
|
+
links.append(("/", "project overview"))
|
|
258
269
|
return links
|
|
259
270
|
|
|
260
271
|
|
|
261
|
-
def _watch_html(
|
|
272
|
+
def _watch_html(
|
|
273
|
+
svg_url, order_by_date, target_task=None, filter_completed=False
|
|
274
|
+
):
|
|
262
275
|
# Keep the SVG as the page's main content so browser zoom behavior matches
|
|
263
276
|
# the original watcher experience (the graph scales, not just footer text).
|
|
264
277
|
footer_html = " | ".join(
|
|
265
278
|
f"<a href='{footer_url}'>{footer_label}</a>"
|
|
266
279
|
for footer_url, footer_label in _watch_footer_links(
|
|
267
|
-
order_by_date, target_task
|
|
280
|
+
order_by_date, target_task, filter_completed
|
|
268
281
|
)
|
|
269
282
|
)
|
|
270
283
|
return (
|
|
@@ -406,6 +419,7 @@ def build_graph(
|
|
|
406
419
|
order_by_date=False,
|
|
407
420
|
prev_data=None,
|
|
408
421
|
target_task=None,
|
|
422
|
+
filter_completed=False,
|
|
409
423
|
resolve_due_date_conflict=None,
|
|
410
424
|
):
|
|
411
425
|
# Graphviz is required for dot -> svg rendering.
|
|
@@ -422,6 +436,7 @@ def build_graph(
|
|
|
422
436
|
old_data=prev_data,
|
|
423
437
|
validate_due_dates=True,
|
|
424
438
|
filter_task=target_task,
|
|
439
|
+
filter_completed=filter_completed,
|
|
425
440
|
resolve_due_date_conflict=resolve_due_date_conflict,
|
|
426
441
|
)
|
|
427
442
|
subprocess.run(
|
|
@@ -437,37 +452,10 @@ def build_graph(
|
|
|
437
452
|
_svg_add_task_links(svg_root, namespace)
|
|
438
453
|
|
|
439
454
|
if not order_by_date:
|
|
440
|
-
# In dependency view mode, each
|
|
441
|
-
#
|
|
442
|
-
#
|
|
443
|
-
|
|
444
|
-
endpoints = set()
|
|
445
|
-
for name, node_data in data["nodes"].items():
|
|
446
|
-
if node_data.get("style") == "endpoint":
|
|
447
|
-
endpoints.add(name)
|
|
448
|
-
|
|
449
|
-
projects = {}
|
|
450
|
-
for endpoint in sorted(endpoints):
|
|
451
|
-
projects[endpoint] = [endpoint]
|
|
452
|
-
ancestors_to_visit = []
|
|
453
|
-
if "parents" in data["nodes"][endpoint]:
|
|
454
|
-
for parent in data["nodes"][endpoint]["parents"]:
|
|
455
|
-
ancestors_to_visit.append(parent)
|
|
456
|
-
already_seen = set([endpoint])
|
|
457
|
-
while ancestors_to_visit:
|
|
458
|
-
ancestor = ancestors_to_visit.pop()
|
|
459
|
-
if ancestor in already_seen:
|
|
460
|
-
continue
|
|
461
|
-
already_seen.add(ancestor)
|
|
462
|
-
if ancestor in endpoints:
|
|
463
|
-
continue
|
|
464
|
-
projects[endpoint].append(ancestor)
|
|
465
|
-
if (
|
|
466
|
-
ancestor in data["nodes"]
|
|
467
|
-
and "parents" in data["nodes"][ancestor]
|
|
468
|
-
):
|
|
469
|
-
for parent in data["nodes"][ancestor]["parents"]:
|
|
470
|
-
ancestors_to_visit.append(parent)
|
|
455
|
+
# In dependency view mode, each endpoint style defines a project
|
|
456
|
+
# color. A project includes the endpoint plus ancestors, but stops
|
|
457
|
+
# before any ancestor that is itself an endpoint.
|
|
458
|
+
projects = endpoint_projects(data)
|
|
471
459
|
|
|
472
460
|
title_to_group = {}
|
|
473
461
|
node_title_to_group = {}
|
|
@@ -664,7 +652,11 @@ class GraphEventHandler(FileSystemEventHandler):
|
|
|
664
652
|
self.data = data
|
|
665
653
|
self.resolve_due_date_conflict = resolve_due_date_conflict
|
|
666
654
|
if state is None:
|
|
667
|
-
self.state = {
|
|
655
|
+
self.state = {
|
|
656
|
+
"order_by_date": False,
|
|
657
|
+
"target_task": None,
|
|
658
|
+
"filter_completed": False,
|
|
659
|
+
}
|
|
668
660
|
else:
|
|
669
661
|
self.state = state
|
|
670
662
|
self.debounce = debounce
|
|
@@ -686,6 +678,8 @@ class GraphEventHandler(FileSystemEventHandler):
|
|
|
686
678
|
build_kwargs["resolve_due_date_conflict"] = (
|
|
687
679
|
self.resolve_due_date_conflict
|
|
688
680
|
)
|
|
681
|
+
if self.state["filter_completed"]:
|
|
682
|
+
build_kwargs["filter_completed"] = True
|
|
689
683
|
self.data = build_graph(
|
|
690
684
|
self.yaml_file,
|
|
691
685
|
self.dot_file,
|
|
@@ -775,21 +769,30 @@ class FlowchartPreviewServer:
|
|
|
775
769
|
params = urllib.parse.parse_qs(
|
|
776
770
|
parsed.query, keep_blank_values=True
|
|
777
771
|
)
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
772
|
+
(
|
|
773
|
+
order_by_date,
|
|
774
|
+
target_task,
|
|
775
|
+
filter_completed,
|
|
776
|
+
) = _watch_view_state_from_params(params)
|
|
781
777
|
|
|
782
778
|
if (
|
|
783
779
|
order_by_date != event_handler.state["order_by_date"]
|
|
784
780
|
or target_task != event_handler.state["target_task"]
|
|
781
|
+
or filter_completed
|
|
782
|
+
!= event_handler.state["filter_completed"]
|
|
785
783
|
):
|
|
786
784
|
event_handler.state["order_by_date"] = order_by_date
|
|
787
785
|
event_handler.state["target_task"] = target_task
|
|
786
|
+
event_handler.state["filter_completed"] = (
|
|
787
|
+
filter_completed
|
|
788
|
+
)
|
|
788
789
|
build_kwargs = {}
|
|
789
790
|
if event_handler.resolve_due_date_conflict is not None:
|
|
790
791
|
build_kwargs["resolve_due_date_conflict"] = (
|
|
791
792
|
event_handler.resolve_due_date_conflict
|
|
792
793
|
)
|
|
794
|
+
if event_handler.state["filter_completed"]:
|
|
795
|
+
build_kwargs["filter_completed"] = True
|
|
793
796
|
event_handler.data = build_graph(
|
|
794
797
|
event_handler.yaml_file,
|
|
795
798
|
event_handler.dot_file,
|
|
@@ -805,6 +808,7 @@ class FlowchartPreviewServer:
|
|
|
805
808
|
"/graph.svg",
|
|
806
809
|
event_handler.state["order_by_date"],
|
|
807
810
|
event_handler.state["target_task"],
|
|
811
|
+
event_handler.state["filter_completed"],
|
|
808
812
|
)
|
|
809
813
|
body_bytes = body.encode("utf-8")
|
|
810
814
|
_send_preview_response(
|
|
@@ -850,10 +854,11 @@ class FlowchartPreviewServer:
|
|
|
850
854
|
"wrap_width": "Line wrap width used when generating node labels",
|
|
851
855
|
"d": "Render nodes by date without showing connections",
|
|
852
856
|
"t": "Task name to focus on (show incomplete ancestor tasks only)",
|
|
857
|
+
"p": "Render the full plan with completed tasks filtered out",
|
|
853
858
|
},
|
|
854
859
|
filename_extensions={"yaml": (".yaml", ".yml")},
|
|
855
860
|
)
|
|
856
|
-
def wgrph(yaml, wrap_width=55, d=False, t=None):
|
|
861
|
+
def wgrph(yaml, wrap_width=55, d=False, t=None, p=False):
|
|
857
862
|
# Selenium is only required when actually launching the watcher, so it is
|
|
858
863
|
# imported here to avoid breaking the command-line tools when the optional
|
|
859
864
|
# dependency is not installed.
|
|
@@ -875,7 +880,11 @@ def wgrph(yaml, wrap_width=55, d=False, t=None):
|
|
|
875
880
|
|
|
876
881
|
# The browser now drives filtering/date-mode by GET query parameters
|
|
877
882
|
# handled by the local preview server. Keep Python state in sync there.
|
|
878
|
-
initial_state = {
|
|
883
|
+
initial_state = {
|
|
884
|
+
"order_by_date": False,
|
|
885
|
+
"target_task": None,
|
|
886
|
+
"filter_completed": False,
|
|
887
|
+
}
|
|
879
888
|
|
|
880
889
|
# Build the default dependency graph first. Optional -t / -d args are then
|
|
881
890
|
# applied by requesting server URLs with query parameters.
|
|
@@ -887,7 +896,7 @@ def wgrph(yaml, wrap_width=55, d=False, t=None):
|
|
|
887
896
|
initial_state["order_by_date"],
|
|
888
897
|
None,
|
|
889
898
|
initial_state["target_task"],
|
|
890
|
-
_resolve_due_date_conflict_with_qt,
|
|
899
|
+
resolve_due_date_conflict=_resolve_due_date_conflict_with_qt,
|
|
891
900
|
)
|
|
892
901
|
|
|
893
902
|
options = Options()
|
|
@@ -921,6 +930,8 @@ def wgrph(yaml, wrap_width=55, d=False, t=None):
|
|
|
921
930
|
)
|
|
922
931
|
elif d:
|
|
923
932
|
driver.get(preview_server.base_url + "?d=1")
|
|
933
|
+
elif p:
|
|
934
|
+
driver.get(preview_server.base_url + "?p=1")
|
|
924
935
|
|
|
925
936
|
observer = Observer()
|
|
926
937
|
observer.schedule(event_handler, yaml_file.parent, recursive=False)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/.jupyter_cache/__version__.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/.jupyter_cache/global.db
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/subproject1/end_vs_he_sketch.jpg
RENAMED
|
File without changes
|
{pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/subproject1/independent.qmd
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/subproject1/test_include.qmd
RENAMED
|
File without changes
|
{pydifftools-0.1.27 → pydifftools-0.1.28}/example_notebook/project1/subproject1/tryforerror.qmd
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|