scriptplan 0.10.2__tar.gz → 0.10.3__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.
- {scriptplan-0.10.2 → scriptplan-0.10.3}/CHANGELOG.md +154 -0
- {scriptplan-0.10.2/scriptplan.egg-info → scriptplan-0.10.3}/PKG-INFO +51 -12
- {scriptplan-0.10.2 → scriptplan-0.10.3}/README.md +50 -11
- scriptplan-0.10.3/examples/edge_cases/daily_cap_counts_worked_time.expected.csv +4 -0
- scriptplan-0.10.3/examples/edge_cases/daily_cap_counts_worked_time.expected.md +8 -0
- scriptplan-0.10.3/examples/edge_cases/eight_efforts_fill_whole_days.expected.csv +9 -0
- scriptplan-0.10.3/examples/edge_cases/eight_efforts_fill_whole_days.expected.md +6 -0
- scriptplan-0.10.3/examples/edge_cases/shared_slot_backward.expected.csv +4 -0
- scriptplan-0.10.3/examples/edge_cases/shared_slot_backward.expected.md +6 -0
- scriptplan-0.10.3/examples/edge_cases/shared_slot_forward_5min.expected.csv +4 -0
- scriptplan-0.10.3/examples/edge_cases/shared_slot_forward_5min.expected.md +8 -0
- scriptplan-0.10.3/examples/edge_cases/shared_slot_forward_60min.expected.csv +4 -0
- scriptplan-0.10.3/examples/edge_cases/shared_slot_forward_60min.expected.md +8 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/__init__.py +1 -1
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/cli/main.py +1 -46
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/cli/plan.py +1 -1
- scriptplan-0.10.3/scriptplan/cli/programmatic.py +61 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/core/allocation_plan.py +22 -9
- scriptplan-0.10.3/scriptplan/core/criticalness.py +124 -0
- scriptplan-0.10.3/scriptplan/core/dated_tasks.py +83 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/core/limits.py +2 -1
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/core/project.py +9 -30
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/core/project_calendar.py +73 -24
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/core/project_tree.py +13 -1
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/core/property_tree_node.py +15 -9
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/core/resolution.py +7 -2
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/core/resource_availability.py +22 -21
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/core/task_dependencies.py +19 -3
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/core/working_hours.py +8 -2
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/parser/attribute_apply.py +17 -8
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/parser/model_pieces.py +11 -7
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/parser/tjp.lark +3 -2
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/parser/tjp_parser.py +34 -8
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/parser/transformer_resource.py +4 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/parser/transformer_task.py +5 -1
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/scheduler/postcheck.py +42 -8
- {scriptplan-0.10.2 → scriptplan-0.10.3/scriptplan.egg-info}/PKG-INFO +51 -12
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan.egg-info/SOURCES.txt +8 -0
- scriptplan-0.10.3/tests/test_container_dependencies.py +244 -0
- scriptplan-0.10.3/tests/test_contention_and_bookings.py +168 -0
- scriptplan-0.10.3/tests/test_project_calendar.py +150 -0
- scriptplan-0.10.3/tests/test_reference_and_duration.py +69 -0
- scriptplan-0.10.3/tests/test_scope_and_inherited_start.py +86 -0
- scriptplan-0.10.2/examples/edge_cases/daily_cap_counts_worked_time.expected.csv +0 -4
- scriptplan-0.10.2/examples/edge_cases/daily_cap_counts_worked_time.expected.md +0 -6
- scriptplan-0.10.2/examples/edge_cases/eight_efforts_fill_whole_days.expected.csv +0 -9
- scriptplan-0.10.2/examples/edge_cases/eight_efforts_fill_whole_days.expected.md +0 -5
- scriptplan-0.10.2/examples/edge_cases/shared_slot_backward.expected.csv +0 -4
- scriptplan-0.10.2/examples/edge_cases/shared_slot_backward.expected.md +0 -4
- scriptplan-0.10.2/examples/edge_cases/shared_slot_forward_5min.expected.csv +0 -4
- scriptplan-0.10.2/examples/edge_cases/shared_slot_forward_5min.expected.md +0 -6
- scriptplan-0.10.2/examples/edge_cases/shared_slot_forward_60min.expected.csv +0 -4
- scriptplan-0.10.2/examples/edge_cases/shared_slot_forward_60min.expected.md +0 -6
- {scriptplan-0.10.2 → scriptplan-0.10.3}/LICENSE +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/MANIFEST.in +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/README.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/alap_before_declared_start.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/alap_before_declared_start.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/alap_before_declared_start.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/alap_bounded_by_declared_starts.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/alap_bounded_by_declared_starts.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/alap_bounded_by_declared_starts.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/alap_gapduration_before_successor.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/alap_gapduration_before_successor.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/alap_gapduration_before_successor.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/alap_keeps_successor_gaps.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/alap_keeps_successor_gaps.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/alap_keeps_successor_gaps.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/alap_milestones_exact.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/alap_milestones_exact.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/alap_milestones_exact.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/alap_onstart_reading.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/alap_onstart_reading.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/alap_onstart_reading.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/alap_own_end_before_successor.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/alap_own_end_before_successor.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/alap_own_end_before_successor.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/alap_project_end_and_declared_end.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/alap_project_end_and_declared_end.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/alap_project_end_and_declared_end.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/alap_spans_before_mid_slot_successor.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/alap_spans_before_mid_slot_successor.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/alap_spans_before_mid_slot_successor.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/alap_without_anchor_parent.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/alap_without_anchor_parent.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/alap_without_anchor_parent.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/alap_without_anchor_plan.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/alap_without_anchor_plan.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/alap_without_anchor_plan.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/asap_and_alap_share_a_slot.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/asap_and_alap_share_a_slot.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/asap_and_alap_share_a_slot.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/daily_cap_counts_worked_time.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/dependency_offset_one_slot.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/dependency_offset_one_slot.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/dependency_offset_one_slot.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/duration_length_after_mid_slot_end.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/duration_length_after_mid_slot_end.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/duration_length_after_mid_slot_end.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/eight_efforts_fill_whole_days.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/gaplength_on_project_calendar.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/gaplength_on_project_calendar.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/gaplength_on_project_calendar.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/inherited_start_waits_for_dependency.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/inherited_start_waits_for_dependency.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/inherited_start_waits_for_dependency.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/mid_slot_start_and_end.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/mid_slot_start_and_end.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/mid_slot_start_and_end.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/shared_slot_backward.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/shared_slot_forward_5min.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/shared_slot_forward_60min.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/skipped_time_stays_free.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/skipped_time_stays_free.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/skipped_time_stays_free.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/two_people_finish_together.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/two_people_finish_together.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/edge_cases/two_people_finish_together.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/airport_retrofit.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/airport_retrofit.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/airport_retrofit.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/airport_stress_test.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/airport_stress_test.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/airport_stress_test.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/airport_ultra_math.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/airport_ultra_math.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/airport_ultra_math.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/airport_ultra_math_report.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/airport_ultra_math_report.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/airport_ultra_math_report.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/alap_backward.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/alap_backward.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/alap_backward.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/atomic.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/atomic.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/atomic.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/blackbox.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/blackbox.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/blackbox.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/bottleneck.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/bottleneck.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/bottleneck.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/eclipse.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/eclipse.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/eclipse.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/failover.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/failover.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/failover.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/jit_supply.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/jit_supply.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/jit_supply.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/math_torture.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/math_torture.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/math_torture.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/paradox.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/paradox.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/paradox.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/priority_clash.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/priority_clash.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/priority_clash.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/quota.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/quota.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/quota.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/simple.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/simple.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/simple.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/synchrony.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/synchrony.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/synchrony.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/thermal.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/thermal.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/thermal.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/throughput.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/throughput.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/throughput.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/time_traveler.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/time_traveler.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/time_traveler.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/timezone_stress.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/timezone_stress.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/timezone_stress.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/tutorial.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/tutorial.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/tutorial.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/union_contract.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/union_contract.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/union_contract.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/workflow_engine.expected.csv +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/workflow_engine.expected.md +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/showcase/workflow_engine.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/examples/simple.tjp +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/pyproject.toml +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/cli/__init__.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/cli/plan_files.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/cli/plan_help.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/core/__init__.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/core/account.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/core/allocation.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/core/attributes.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/core/booking.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/core/declared_dates.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/core/exceptions.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/core/journal.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/core/leave.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/core/project_attributes.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/core/property.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/core/property_list.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/core/property_set.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/core/resource.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/core/resource_reporting.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/core/resource_scenario.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/core/scenario.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/core/scenario_data.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/core/shift.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/core/slot_ledger.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/core/task.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/core/task_booking.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/core/task_placement.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/core/task_resources.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/core/task_scenario.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/core/task_spans.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/core/timesheet.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/core/timezones.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/core/units.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/parser/__init__.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/parser/macro_processor.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/parser/report_builder.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/parser/transformer_project.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/parser/transformer_report.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/py.typed +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/report/__init__.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/report/logical_expression.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/report/report.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/report/report_base.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/report/report_context.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/report/resource_report.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/report/table_cells.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/report/table_headers.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/report/table_report.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/report/task_report.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/report/text_report.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/scheduler/__init__.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/scheduler/preflight.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/scheduler/scoreboard.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/scheduler/stalls.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/utils/__init__.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/utils/data_cache.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/utils/logger.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/utils/message_handler.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/utils/messages.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan/utils/time.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan.egg-info/dependency_links.txt +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan.egg-info/entry_points.txt +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan.egg-info/requires.txt +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/scriptplan.egg-info/top_level.txt +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/setup.cfg +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/setup.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/tests/test_account.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/tests/test_ansi_color.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/tests/test_attribute_base.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/tests/test_audit_probes.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/tests/test_cli.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/tests/test_core_allocation.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/tests/test_core_booking.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/tests/test_core_project.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/tests/test_core_resource.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/tests/test_core_scenario.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/tests/test_core_task.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/tests/test_dependency_check.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/tests/test_examples.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/tests/test_failure_messages.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/tests/test_fixture_golden.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/tests/test_journal.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/tests/test_macro_processor.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/tests/test_message_handler.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/tests/test_parser.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/tests/test_plan_cli.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/tests/test_project_isolation.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/tests/test_property_list.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/tests/test_property_set.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/tests/test_property_tree_node.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/tests/test_report.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/tests/test_scheduling.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/tests/test_scheduling_validation.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/tests/test_scoreboard.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/tests/test_shift.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/tests/test_slot_ledger.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/tests/test_stated_dates.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/tests/test_threads.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/tests/test_timesheet.py +0 -0
- {scriptplan-0.10.2 → scriptplan-0.10.3}/tests/test_utils_time.py +0 -0
|
@@ -1,5 +1,159 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.10.3
|
|
4
|
+
|
|
5
|
+
Fixes the defects Trace and Siambr reported against 0.10.2, and those found while fixing
|
|
6
|
+
them. Two are regressions, one in 0.10.2 and one in 0.10.0. Three of the fixes can move
|
|
7
|
+
the dates of a project that already scheduled, and they are listed first.
|
|
8
|
+
|
|
9
|
+
### Fixes that can move an existing schedule
|
|
10
|
+
|
|
11
|
+
- Tasks of equal priority were placed in declaration order. The README has always said
|
|
12
|
+
they are placed by criticalness, which no code computed until this release. A
|
|
13
|
+
resource's criticalness is the effort asked of it divided by the time it has. A task's
|
|
14
|
+
is its effort times the mean criticalness of the resources it may use, and its path
|
|
15
|
+
criticalness adds the heaviest chain of tasks that follow it. In practice the larger
|
|
16
|
+
effort is placed first. Tasks of 4 h, 1 h and 4 h on one desk used to run t0, t1, t2
|
|
17
|
+
and now run t0, t2, t1, which is the reference scheduler's order forwards, backwards
|
|
18
|
+
and under a daily cap. Give a task a `priority` to fix its place.
|
|
19
|
+
- A task with a stated `start` and `end` and no effort booked none of the resources it
|
|
20
|
+
allocates. With `a` at priority 600 from 18:00 to 20:00 on a resource and `w` needing
|
|
21
|
+
an hour of the same resource from 18:00, 0.10.2 placed `w` at 18:00, inside `a`. `a`
|
|
22
|
+
now holds the resource and `w` runs 20:00-21:00. Where two such tasks overlap on one
|
|
23
|
+
resource, the one placed first holds the shared hours. The other keeps its dates and
|
|
24
|
+
books what is left, and neither is an error.
|
|
25
|
+
- A project's `workinghours`, a global `leaves` and a shift's `leaves` now take effect
|
|
26
|
+
(next section). A project that sets any of them gets different dates, and they are
|
|
27
|
+
the dates it asked for.
|
|
28
|
+
|
|
29
|
+
### Calendar
|
|
30
|
+
|
|
31
|
+
Siambr reported the first three.
|
|
32
|
+
|
|
33
|
+
- A project's `workinghours` was parsed and never applied. `length`, `gaplength` and a
|
|
34
|
+
resource with no hours of its own used Monday to Friday 09:00-17:00 whatever the
|
|
35
|
+
project said. With `workinghours mon - sat 09:00 - 17:00`, three days from Thursday
|
|
36
|
+
3 December ended on Monday the 7th. They now end on Saturday the 5th.
|
|
37
|
+
- A global `leaves` did not close the calendar that `length` and `gaplength` count in,
|
|
38
|
+
although a global `vacation` did. `length 3d` from 24 December with
|
|
39
|
+
`leaves holiday "Christmas" 2026-12-25` ended on the 28th. It now ends on the 29th,
|
|
40
|
+
for any leave type and for a leave of part of a day.
|
|
41
|
+
- A shift's `leaves` was dropped when the file was read, because the parser kept the
|
|
42
|
+
word `holiday` and lost the date. A resource on the shift now takes the shift's leave
|
|
43
|
+
with its hours.
|
|
44
|
+
- A global `vacation` in the first week of a project in UTC was repeated every week for
|
|
45
|
+
a resource on the default hours. Ten days of effort from Monday 7 December with a
|
|
46
|
+
vacation on the 8th ended on the 23rd. They now end on the 21st.
|
|
47
|
+
- Because each `leaves`, `vacation` or `booking` line on a resource stored the earlier
|
|
48
|
+
ones again, N lines kept 2^N - 1 entries and an availability check walked them all.
|
|
49
|
+
The dates were right, but the time doubled with each line, and Siambr measured 58
|
|
50
|
+
seconds for 20 leave days. Twenty lines now store twenty entries and schedule in
|
|
51
|
+
half a second.
|
|
52
|
+
- A day that no `workinghours` line of a block names is not worked, for a project as
|
|
53
|
+
for a shift or a resource. This is unchanged and is now written down, because other
|
|
54
|
+
schedulers of this format keep the inherited hours on such a day.
|
|
55
|
+
|
|
56
|
+
### Accepted and ignored
|
|
57
|
+
|
|
58
|
+
`audit/evaluations/probe_directive_effect.py` now checks every directive the grammar
|
|
59
|
+
accepts. It found these, beyond the calendar ones above.
|
|
60
|
+
|
|
61
|
+
- Allocating a resource that has members failed with "could not be scheduled". It now
|
|
62
|
+
allocates every member, and 16 hours on a group of two takes one day.
|
|
63
|
+
- `allocate r { limits { ... } }` was parsed and never applied. It is now refused, and
|
|
64
|
+
the error says to put the limits on the task.
|
|
65
|
+
- `dailymin`, `weeklymin`, `monthlymin` and `minimum` were counted and never reported.
|
|
66
|
+
A period in which a resource or task was booked for less than its minimum now draws
|
|
67
|
+
a warning. The schedule and the exit code are unchanged.
|
|
68
|
+
- `plan report` now prints warnings to stderr when the run succeeds. It used to discard
|
|
69
|
+
them, including the one that says a task asked to be scheduled as late as possible
|
|
70
|
+
and was scheduled as soon as possible because nothing fixed when it must end.
|
|
71
|
+
- An attribute the reader could not turn into a name and a value was skipped. It is an
|
|
72
|
+
error now. The one case left on purpose is the value of a custom attribute declared
|
|
73
|
+
with `extend`, which ScriptPlan does not keep (README, known limits).
|
|
74
|
+
|
|
75
|
+
### Regressions
|
|
76
|
+
|
|
77
|
+
- A `!` reference whose scope is a task bound a task elsewhere in the project when that
|
|
78
|
+
scope had no such child. `g.b` with `depends !a` and no sibling `a` was scheduled after
|
|
79
|
+
`h.a`, an unrelated task in another branch. 0.10.1 refused the file, and 0.10.3
|
|
80
|
+
refuses it again. A reference with no `!`, or whose `!`s reach the top level of the
|
|
81
|
+
project, still binds the only task with that id, and one that could mean two tasks is
|
|
82
|
+
still refused. This was a regression in 0.10.2.
|
|
83
|
+
- A task that depends on a container nested two or more levels deep never started. With
|
|
84
|
+
`p { m { w } }` and `s depends p`, 0.10.0 to 0.10.2 reported "Task 's' could not be
|
|
85
|
+
scheduled: no ready predecessor or successor". Containers were marked done in one
|
|
86
|
+
pass in declaration order, so p was checked before m and never marked. s now starts
|
|
87
|
+
09:30, when w ends, as 0.9.2 and the reference scheduler place it.
|
|
88
|
+
|
|
89
|
+
### Fixed
|
|
90
|
+
|
|
91
|
+
- A container that depends on another with a gap, `gaplength` or `onstart` never
|
|
92
|
+
scheduled its children. `q { depends p { gapduration 30min } }` reported "Task 'q.c'
|
|
93
|
+
could not be scheduled: no ready predecessor or successor" in 0.10.0 to 0.10.2, and
|
|
94
|
+
0.9.2 left q.c without dates and said nothing. Inheriting the dependency copied the
|
|
95
|
+
task `p` it names, and q.c waited for the copy, which is never scheduled. q.c now
|
|
96
|
+
runs 10:00-11:00, 30 minutes after p ends, which is the reference scheduler's answer.
|
|
97
|
+
An attribute that names a task, resource, account or shift keeps naming that same
|
|
98
|
+
one when a child inherits it.
|
|
99
|
+
- A task scheduled as late as possible inside a container was not bounded by a
|
|
100
|
+
successor that depends on the container. With `q depends p` and q.c ending Monday
|
|
101
|
+
17:30, 0.9.2 to 0.10.1 placed p.p1 on Friday the 20th, after q.c, and said nothing.
|
|
102
|
+
0.10.2 refused the plan. p.p1 now ends when q.c starts, or 30 minutes earlier with
|
|
103
|
+
`gapduration 30min`. The same holds for a task nested deeper inside p.
|
|
104
|
+
- `precedes` dropped its options. `a precedes b { gapduration 30min }` let b start the
|
|
105
|
+
moment a ended, and `gaplength` and `maxgapduration` were dropped the same way, with
|
|
106
|
+
nothing said. They now hold b back exactly as `b depends a` with the same option
|
|
107
|
+
does, in both directions; the backward cases match the reference scheduler.
|
|
108
|
+
`precedes b { onend }` is refused, because ScriptPlan cannot tie a task to another's
|
|
109
|
+
end, where 0.10.2 dropped it.
|
|
110
|
+
- When a task broke a dependency on a container, the error was printed twice, and it
|
|
111
|
+
quoted the task's actual start as the start the file declares. It is printed once,
|
|
112
|
+
and names the declared start when that differs.
|
|
113
|
+
- A milestone took a start inherited from its container as fixed. With the container
|
|
114
|
+
starting Monday 09:00 and the milestone depending on a task that ends at 17:00, 0.10.1
|
|
115
|
+
dated the milestone 09:00 and 0.10.2 refused the plan. The inherited start is now an
|
|
116
|
+
earliest start, as it already was for tasks with work, so the milestone sits at 17:00.
|
|
117
|
+
A start stated on the milestone itself is still fixed.
|
|
118
|
+
- An unresolved `depends` or `precedes` was reported as "not a task in this project",
|
|
119
|
+
even when a task with that id existed elsewhere. The error now says the reference
|
|
120
|
+
names no task from where it is written.
|
|
121
|
+
|
|
122
|
+
### Documentation
|
|
123
|
+
|
|
124
|
+
- README now says that a task ranked higher takes a resource's daily or weekly
|
|
125
|
+
allowance first, so a lower-ranked task that started earlier can finish later. Trace
|
|
126
|
+
asked about a task that waited two days for 15 minutes on a capped resource. 0.10.2
|
|
127
|
+
and the hour-level reference scheduler give that schedule, and 0.10.1 gives it too at
|
|
128
|
+
15-minute slots. 0.10.1 differed only at 30-minute slots, because of the skipped-time
|
|
129
|
+
regression that 0.10.2 fixed.
|
|
130
|
+
|
|
131
|
+
### Verification
|
|
132
|
+
|
|
133
|
+
`tests/test_scope_and_inherited_start.py` runs six plans through the `plan` CLI. The
|
|
134
|
+
three for the defects fail on 0.10.2, and the three that check what must not change pass
|
|
135
|
+
on both versions. `tests/test_container_dependencies.py` runs 18 plans with container
|
|
136
|
+
dependencies and `precedes`, checked against the reference scheduler where it accepts
|
|
137
|
+
the plan and worked out by hand where it does not, and all 18 fail on 0.10.2.
|
|
138
|
+
|
|
139
|
+
`tests/test_project_calendar.py` and `tests/test_contention_and_bookings.py` run 33
|
|
140
|
+
plans for the calendar, booking and ordering fixes. Their expected dates are the
|
|
141
|
+
reference scheduler's, apart from the one test of the unnamed-days rule. 28 of them
|
|
142
|
+
fail on 0.10.2 and the 5 that check what must not change pass on both.
|
|
143
|
+
`probe_directive_effect.py` holds a pair of projects for each of 52 directives and
|
|
144
|
+
fails on 0.10.2 for nine of them. 150 random projects with contended resources give the
|
|
145
|
+
reference scheduler's dates exactly. Before the ordering fix, 8 of 80 did not.
|
|
146
|
+
|
|
147
|
+
No golden snapshot changes, but five example answers in `examples/edge_cases` do,
|
|
148
|
+
because they assumed declaration order between tasks of equal priority:
|
|
149
|
+
`shared_slot_forward_5min`, `shared_slot_forward_60min`, `shared_slot_backward`,
|
|
150
|
+
`daily_cap_counts_worked_time` and `eight_efforts_fill_whole_days`. Four probes that
|
|
151
|
+
assumed the same are updated. Trace's 16 project files give the same result as on
|
|
152
|
+
0.10.2, apart from the two this release was meant to fix.
|
|
153
|
+
|
|
154
|
+
Free-threaded Python builds were not exercised, nor Python 3.10 to 3.12 for the full
|
|
155
|
+
suite.
|
|
156
|
+
|
|
3
157
|
## 0.10.2
|
|
4
158
|
|
|
5
159
|
0.10.1 shipped two regressions. This release fixes them and the gaps 0.10.1 listed as
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scriptplan
|
|
3
|
-
Version: 0.10.
|
|
3
|
+
Version: 0.10.3
|
|
4
4
|
Summary: A precise project scheduling engine with minute-level accuracy for resource allocation and dependency management.
|
|
5
5
|
Author-email: Farshid Ashouri <farsheed.ashouri@gmail.com>
|
|
6
6
|
Maintainer-email: Farshid Ashouri <farsheed.ashouri@gmail.com>
|
|
@@ -49,8 +49,8 @@ Dynamic: license-file
|
|
|
49
49
|
|
|
50
50
|
# ScriptPlan
|
|
51
51
|
|
|
52
|
-

|
|
53
|
+

|
|
54
54
|

|
|
55
55
|

|
|
56
56
|
|
|
@@ -150,6 +150,12 @@ audit/evaluations/run_all.sh
|
|
|
150
150
|
- Dates are read in the project's timezone and scheduled in UTC; reports convert back.
|
|
151
151
|
- Resource shifts follow their own timezone across daylight-saving changes in both directions.
|
|
152
152
|
- A working-hours interval that crosses midnight applies to the following day.
|
|
153
|
+
- The project's `workinghours` is the calendar that `length`, `gaplength` and a
|
|
154
|
+
resource with no hours of its own use. Without one it is Monday to Friday,
|
|
155
|
+
09:00-17:00, in the project's timezone.
|
|
156
|
+
- A global `leaves` or `vacation` closes that calendar for everyone, whatever the
|
|
157
|
+
leave is called, for whole days or part of one. A shift's `leaves` holds for
|
|
158
|
+
every resource on the shift.
|
|
153
159
|
- Slot size is derived from the times a project uses, so `start 09:30` is honoured
|
|
154
160
|
exactly and a shift of `08:13 - 11:59, 13:07 - 17:47` yields its true 8h26m a day.
|
|
155
161
|
`timingresolution` overrides this.
|
|
@@ -165,9 +171,17 @@ audit/evaluations/run_all.sh
|
|
|
165
171
|
- `effort` is person-time: two resources available in a slot contribute two
|
|
166
172
|
slots of it. `mandatory` requires a resource for the task to progress at all;
|
|
167
173
|
`persistent` keeps the one first chosen; `alternative` falls back.
|
|
168
|
-
-
|
|
169
|
-
|
|
170
|
-
|
|
174
|
+
- Allocating a resource that has members allocates every member.
|
|
175
|
+
- A task with a `duration`, or with a stated `start` and `end`, books the
|
|
176
|
+
resources it allocates for the hours they are on shift and free. It keeps its
|
|
177
|
+
dates when they are not.
|
|
178
|
+
- `dailymax`, `weeklymax`, `monthlymax` and `maximum` block at the cap **and**
|
|
179
|
+
release in the next period. Periods are counted in the resource's own calendar,
|
|
180
|
+
a week runs Monday to Sunday, and a cap smaller than one slot is honoured
|
|
181
|
+
exactly.
|
|
182
|
+
- `dailymin`, `weeklymin`, `monthlymin` and `minimum` are targets, which placing
|
|
183
|
+
tasks cannot guarantee. A period in which the resource or task was booked for
|
|
184
|
+
less draws a warning once the schedule is complete.
|
|
171
185
|
|
|
172
186
|
**Scheduling**
|
|
173
187
|
|
|
@@ -177,6 +191,12 @@ audit/evaluations/run_all.sh
|
|
|
177
191
|
end. With none of these but a successor scheduled as soon as possible that
|
|
178
192
|
states no start, it is scheduled as soon as possible instead, with a warning.
|
|
179
193
|
- Contention is resolved by priority, then criticalness, then declaration order.
|
|
194
|
+
A task's criticalness is its effort times how much is asked of the resources it
|
|
195
|
+
may use, plus that of the heaviest chain of tasks that follow it, so at equal
|
|
196
|
+
priority the larger effort is placed first. Tasks are placed one at a time in
|
|
197
|
+
that order, so a task ranked higher takes a resource's daily or weekly allowance
|
|
198
|
+
first, and a lower-ranked task that started earlier can finish later. Raising
|
|
199
|
+
its priority keeps it going.
|
|
180
200
|
- The declared project period is never rewritten. A task that does not fit is
|
|
181
201
|
reported, with its name and the reason. A date stated outside the period is an
|
|
182
202
|
error.
|
|
@@ -194,7 +214,10 @@ audit/evaluations/run_all.sh
|
|
|
194
214
|
- A project that cannot be scheduled exits non-zero and says why. A dependency
|
|
195
215
|
cycle is rejected before scheduling, naming the cycle.
|
|
196
216
|
- An attribute or reference the parser cannot place is an error, never a silent
|
|
197
|
-
drop.
|
|
217
|
+
drop. Each directive the grammar accepts has a probe showing that it changes a
|
|
218
|
+
schedule, is refused or draws a warning, and the few that do none of these are
|
|
219
|
+
listed in `audit/evaluations/probe_directive_effect.py` with the reason.
|
|
220
|
+
- Warnings are printed to stderr by `plan report` as well as by `scriptplan`.
|
|
198
221
|
- `report_id` is a SHA-256 of the rendered result, so it differs when the
|
|
199
222
|
schedule differs.
|
|
200
223
|
|
|
@@ -205,11 +228,27 @@ audit/evaluations/run_all.sh
|
|
|
205
228
|
rejected at parse time rather than silently producing nothing.
|
|
206
229
|
- Accounts are parsed but not yet modelled, so `chargeset` and `charge` record an
|
|
207
230
|
amount without posting it anywhere.
|
|
208
|
-
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
231
|
+
- The value a task or resource gives to a custom attribute declared with `extend`
|
|
232
|
+
is read and not kept.
|
|
233
|
+
- `limits` inside an `allocate` option block is refused. Put the limits on the
|
|
234
|
+
task, with `{ resources <id> }` to hold them to one resource.
|
|
235
|
+
- The `workinghours` lines of one block (project, shift or resource) state its
|
|
236
|
+
whole week: a day no line names is not worked, and two lines naming the same
|
|
237
|
+
day add up. Other schedulers of this format keep the inherited hours on a day
|
|
238
|
+
a line does not name. Name every day you mean.
|
|
239
|
+
- A `length` task with no start of its own is reported as starting at the first
|
|
240
|
+
working moment it can, where other schedulers report the moment it was
|
|
241
|
+
released, which may be midnight. The end is the same.
|
|
242
|
+
- Each resource holds one entry per slot for the whole project, so memory grows
|
|
243
|
+
with resources times the project's length divided by the slot size. Times off
|
|
244
|
+
the hour make the slots smaller; `timingresolution` sets the size outright.
|
|
245
|
+
- A task reference is looked up from the referring task's scope, one level up for
|
|
246
|
+
each `!`. When the `!`s stop at a task, the reference must name one of its
|
|
247
|
+
children: `!x` with no sibling `x` is refused. When there are no `!`s, or they
|
|
248
|
+
reach the top level of the project and no top-level task matches, the reference
|
|
249
|
+
binds to the only task in the project with that id, so `depends !!pack` inside
|
|
250
|
+
`delivery` finds `delivery.pack`. When two tasks could be meant, the file is
|
|
251
|
+
refused and both are named.
|
|
213
252
|
- `onstart` on a dependency means start-to-start for a task scheduled as soon as
|
|
214
253
|
possible: it may start once the predecessor starts. For a task scheduled as
|
|
215
254
|
late as possible it means the task must finish before the predecessor starts,
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
# ScriptPlan
|
|
6
6
|
|
|
7
|
-

|
|
8
|
+

|
|
9
9
|

|
|
10
10
|

|
|
11
11
|
|
|
@@ -105,6 +105,12 @@ audit/evaluations/run_all.sh
|
|
|
105
105
|
- Dates are read in the project's timezone and scheduled in UTC; reports convert back.
|
|
106
106
|
- Resource shifts follow their own timezone across daylight-saving changes in both directions.
|
|
107
107
|
- A working-hours interval that crosses midnight applies to the following day.
|
|
108
|
+
- The project's `workinghours` is the calendar that `length`, `gaplength` and a
|
|
109
|
+
resource with no hours of its own use. Without one it is Monday to Friday,
|
|
110
|
+
09:00-17:00, in the project's timezone.
|
|
111
|
+
- A global `leaves` or `vacation` closes that calendar for everyone, whatever the
|
|
112
|
+
leave is called, for whole days or part of one. A shift's `leaves` holds for
|
|
113
|
+
every resource on the shift.
|
|
108
114
|
- Slot size is derived from the times a project uses, so `start 09:30` is honoured
|
|
109
115
|
exactly and a shift of `08:13 - 11:59, 13:07 - 17:47` yields its true 8h26m a day.
|
|
110
116
|
`timingresolution` overrides this.
|
|
@@ -120,9 +126,17 @@ audit/evaluations/run_all.sh
|
|
|
120
126
|
- `effort` is person-time: two resources available in a slot contribute two
|
|
121
127
|
slots of it. `mandatory` requires a resource for the task to progress at all;
|
|
122
128
|
`persistent` keeps the one first chosen; `alternative` falls back.
|
|
123
|
-
-
|
|
124
|
-
|
|
125
|
-
|
|
129
|
+
- Allocating a resource that has members allocates every member.
|
|
130
|
+
- A task with a `duration`, or with a stated `start` and `end`, books the
|
|
131
|
+
resources it allocates for the hours they are on shift and free. It keeps its
|
|
132
|
+
dates when they are not.
|
|
133
|
+
- `dailymax`, `weeklymax`, `monthlymax` and `maximum` block at the cap **and**
|
|
134
|
+
release in the next period. Periods are counted in the resource's own calendar,
|
|
135
|
+
a week runs Monday to Sunday, and a cap smaller than one slot is honoured
|
|
136
|
+
exactly.
|
|
137
|
+
- `dailymin`, `weeklymin`, `monthlymin` and `minimum` are targets, which placing
|
|
138
|
+
tasks cannot guarantee. A period in which the resource or task was booked for
|
|
139
|
+
less draws a warning once the schedule is complete.
|
|
126
140
|
|
|
127
141
|
**Scheduling**
|
|
128
142
|
|
|
@@ -132,6 +146,12 @@ audit/evaluations/run_all.sh
|
|
|
132
146
|
end. With none of these but a successor scheduled as soon as possible that
|
|
133
147
|
states no start, it is scheduled as soon as possible instead, with a warning.
|
|
134
148
|
- Contention is resolved by priority, then criticalness, then declaration order.
|
|
149
|
+
A task's criticalness is its effort times how much is asked of the resources it
|
|
150
|
+
may use, plus that of the heaviest chain of tasks that follow it, so at equal
|
|
151
|
+
priority the larger effort is placed first. Tasks are placed one at a time in
|
|
152
|
+
that order, so a task ranked higher takes a resource's daily or weekly allowance
|
|
153
|
+
first, and a lower-ranked task that started earlier can finish later. Raising
|
|
154
|
+
its priority keeps it going.
|
|
135
155
|
- The declared project period is never rewritten. A task that does not fit is
|
|
136
156
|
reported, with its name and the reason. A date stated outside the period is an
|
|
137
157
|
error.
|
|
@@ -149,7 +169,10 @@ audit/evaluations/run_all.sh
|
|
|
149
169
|
- A project that cannot be scheduled exits non-zero and says why. A dependency
|
|
150
170
|
cycle is rejected before scheduling, naming the cycle.
|
|
151
171
|
- An attribute or reference the parser cannot place is an error, never a silent
|
|
152
|
-
drop.
|
|
172
|
+
drop. Each directive the grammar accepts has a probe showing that it changes a
|
|
173
|
+
schedule, is refused or draws a warning, and the few that do none of these are
|
|
174
|
+
listed in `audit/evaluations/probe_directive_effect.py` with the reason.
|
|
175
|
+
- Warnings are printed to stderr by `plan report` as well as by `scriptplan`.
|
|
153
176
|
- `report_id` is a SHA-256 of the rendered result, so it differs when the
|
|
154
177
|
schedule differs.
|
|
155
178
|
|
|
@@ -160,11 +183,27 @@ audit/evaluations/run_all.sh
|
|
|
160
183
|
rejected at parse time rather than silently producing nothing.
|
|
161
184
|
- Accounts are parsed but not yet modelled, so `chargeset` and `charge` record an
|
|
162
185
|
amount without posting it anywhere.
|
|
163
|
-
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
186
|
+
- The value a task or resource gives to a custom attribute declared with `extend`
|
|
187
|
+
is read and not kept.
|
|
188
|
+
- `limits` inside an `allocate` option block is refused. Put the limits on the
|
|
189
|
+
task, with `{ resources <id> }` to hold them to one resource.
|
|
190
|
+
- The `workinghours` lines of one block (project, shift or resource) state its
|
|
191
|
+
whole week: a day no line names is not worked, and two lines naming the same
|
|
192
|
+
day add up. Other schedulers of this format keep the inherited hours on a day
|
|
193
|
+
a line does not name. Name every day you mean.
|
|
194
|
+
- A `length` task with no start of its own is reported as starting at the first
|
|
195
|
+
working moment it can, where other schedulers report the moment it was
|
|
196
|
+
released, which may be midnight. The end is the same.
|
|
197
|
+
- Each resource holds one entry per slot for the whole project, so memory grows
|
|
198
|
+
with resources times the project's length divided by the slot size. Times off
|
|
199
|
+
the hour make the slots smaller; `timingresolution` sets the size outright.
|
|
200
|
+
- A task reference is looked up from the referring task's scope, one level up for
|
|
201
|
+
each `!`. When the `!`s stop at a task, the reference must name one of its
|
|
202
|
+
children: `!x` with no sibling `x` is refused. When there are no `!`s, or they
|
|
203
|
+
reach the top level of the project and no top-level task matches, the reference
|
|
204
|
+
binds to the only task in the project with that id, so `depends !!pack` inside
|
|
205
|
+
`delivery` finds `delivery.pack`. When two tasks could be meant, the file is
|
|
206
|
+
refused and both are named.
|
|
168
207
|
- `onstart` on a dependency means start-to-start for a task scheduled as soon as
|
|
169
208
|
possible: it may start once the predecessor starts. For a task scheduled as
|
|
170
209
|
late as possible it means the task must finish before the predecessor starts,
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# daily_cap_counts_worked_time
|
|
2
|
+
|
|
3
|
+
dailymax 60min on the desk, and three tasks of 40, 13 and 40 minutes at the same priority.
|
|
4
|
+
Tasks of equal priority are placed larger effort first (path criticalness), and equal efforts in
|
|
5
|
+
declaration order, so the order is t0, t2, t1. Monday: t0 works 09:00-09:40, leaving 20 minutes under the
|
|
6
|
+
cap, so t2 works 09:40-10:00 on Monday and its other 20 minutes on Tuesday, 09:00-09:20. t1 follows on
|
|
7
|
+
Tuesday, 09:20-09:33. The cap blocks at exactly 60 minutes and releases on the next day. 0.10.0 counted
|
|
8
|
+
whole booked hours against the cap and spread the tasks over Monday, Tuesday and Wednesday.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
id;start;end
|
|
2
|
+
t0;2026-10-21-16:16:00;2026-10-22-09:17:00
|
|
3
|
+
t1;2026-10-21-12:54:00;2026-10-21-15:03:00
|
|
4
|
+
t2;2026-10-22-09:17:00;2026-10-22-10:06:00
|
|
5
|
+
t3;2026-10-21-15:03:00;2026-10-21-16:16:00
|
|
6
|
+
t4;2026-10-20-10:39:00;2026-10-20-15:57:00
|
|
7
|
+
t5;2026-10-20-15:57:00;2026-10-21-10:40:00
|
|
8
|
+
t6;2026-10-19-09:00:00;2026-10-20-10:39:00
|
|
9
|
+
t7;2026-10-21-10:40:00;2026-10-21-12:54:00
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# eight_efforts_fill_whole_days
|
|
2
|
+
|
|
3
|
+
Eight tasks of 61, 129, 49, 73, 318, 163, 579 and 134 minutes on one desk with 09:00-17:00 days, all at
|
|
4
|
+
the same priority. They are placed larger effort first (path criticalness): t6, t4, t5, t7, t1, t3, t0,
|
|
5
|
+
t2. Packed back to back they fill Monday, Tuesday and Wednesday with exactly 480 minutes each and end on
|
|
6
|
+
Thursday after 66 more (1506 in all). 0.10.0 booked 490 minutes into the Monday.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# shared_slot_backward
|
|
2
|
+
|
|
3
|
+
The same three tasks, each alap with end 17:00. Tasks of equal priority are placed larger effort first (path criticalness), and equal efforts in
|
|
4
|
+
declaration order, so the order is t0, t2, t1.
|
|
5
|
+
Each is placed as late as the desk allows: t0 17:00 - 40 = 16:20; t2 ends where t0 begins,
|
|
6
|
+
16:20 - 40 = 15:40; t1 15:40 - 13 = 15:27.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# shared_slot_forward_5min
|
|
2
|
+
|
|
3
|
+
Three tasks of 40, 13 and 40 minutes on one desk, same priority, 5-minute slots.
|
|
4
|
+
Tasks of equal priority are placed larger effort first (path criticalness), and equal efforts in
|
|
5
|
+
declaration order, so the order is t0, t2, t1. One person works one task at a time, so they run back to back
|
|
6
|
+
from 09:00: t0 09:00 + 40 = 09:40; t2 09:40 + 40 = 10:20; t1 10:20 + 13 = 10:33. Total 93 minutes.
|
|
7
|
+
0.10.0 started all three at 09:00 at 60-minute resolution (issuedb #11). Until 0.10.3 equal priorities
|
|
8
|
+
were placed in declaration order (issuedb #37).
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# shared_slot_forward_60min
|
|
2
|
+
|
|
3
|
+
Three tasks of 40, 13 and 40 minutes on one desk, same priority, 60-minute slots.
|
|
4
|
+
Tasks of equal priority are placed larger effort first (path criticalness), and equal efforts in
|
|
5
|
+
declaration order, so the order is t0, t2, t1. One person works one task at a time, so they run back to back
|
|
6
|
+
from 09:00: t0 09:00 + 40 = 09:40; t2 09:40 + 40 = 10:20; t1 10:20 + 13 = 10:33. Total 93 minutes.
|
|
7
|
+
0.10.0 started all three at 09:00 at 60-minute resolution (issuedb #11). Until 0.10.3 equal priorities
|
|
8
|
+
were placed in declaration order (issuedb #37).
|
|
@@ -7,7 +7,7 @@ for project files written in the .tjp project definition language.
|
|
|
7
7
|
|
|
8
8
|
# The single source of truth for the version; pyproject reads it from here.
|
|
9
9
|
# 0.10.2: every stated date and dependency is kept or refused, never overridden.
|
|
10
|
-
__version__ = "0.10.
|
|
10
|
+
__version__ = "0.10.3"
|
|
11
11
|
__author__ = "ScriptPlan Team"
|
|
12
12
|
|
|
13
13
|
from scriptplan.core.project import Project
|
|
@@ -25,6 +25,7 @@ from pathlib import Path
|
|
|
25
25
|
from typing import Optional
|
|
26
26
|
|
|
27
27
|
from scriptplan import __version__
|
|
28
|
+
from scriptplan.cli.programmatic import run_scriptplan # noqa: F401 (moved; still importable from here)
|
|
28
29
|
|
|
29
30
|
|
|
30
31
|
def setup_logging(debug_level: int = 0, debug_modules: Optional[list[str]] = None) -> None:
|
|
@@ -472,52 +473,6 @@ class ScriptPlan:
|
|
|
472
473
|
return False
|
|
473
474
|
|
|
474
475
|
|
|
475
|
-
def run_scriptplan(tjp_file: str, output_dir: Optional[str] = None) -> tuple[bool, Optional[str]]:
|
|
476
|
-
"""
|
|
477
|
-
Run ScriptPlan report generation on a .tjp file.
|
|
478
|
-
|
|
479
|
-
This is a simplified interface for programmatic use.
|
|
480
|
-
|
|
481
|
-
Args:
|
|
482
|
-
tjp_file: Path to the .tjp file
|
|
483
|
-
output_dir: Optional output directory for reports (default: current directory)
|
|
484
|
-
|
|
485
|
-
Returns:
|
|
486
|
-
Tuple of (success, error_message)
|
|
487
|
-
"""
|
|
488
|
-
import contextlib
|
|
489
|
-
import io
|
|
490
|
-
|
|
491
|
-
# Capture stderr to get error messages
|
|
492
|
-
stderr_capture = io.StringIO()
|
|
493
|
-
|
|
494
|
-
try:
|
|
495
|
-
with contextlib.redirect_stderr(stderr_capture):
|
|
496
|
-
# Create minimal args
|
|
497
|
-
parser = create_parser()
|
|
498
|
-
args_list = [tjp_file]
|
|
499
|
-
if output_dir:
|
|
500
|
-
args_list.extend(["--output-dir", output_dir])
|
|
501
|
-
args = parser.parse_args(args_list)
|
|
502
|
-
|
|
503
|
-
# Suppress logging for programmatic use
|
|
504
|
-
logging.getLogger().setLevel(logging.ERROR)
|
|
505
|
-
|
|
506
|
-
# Run the application
|
|
507
|
-
app = ScriptPlan(args)
|
|
508
|
-
exit_code = app.run()
|
|
509
|
-
|
|
510
|
-
if exit_code == 0:
|
|
511
|
-
return (True, None)
|
|
512
|
-
else:
|
|
513
|
-
error_output = stderr_capture.getvalue()
|
|
514
|
-
return (False, error_output or "Report generation failed")
|
|
515
|
-
|
|
516
|
-
except Exception as e:
|
|
517
|
-
error_output = stderr_capture.getvalue()
|
|
518
|
-
return (False, error_output or str(e))
|
|
519
|
-
|
|
520
|
-
|
|
521
476
|
def main(argv: Optional[list[str]] = None) -> int:
|
|
522
477
|
"""
|
|
523
478
|
Main entry point for the CLI.
|
|
@@ -19,7 +19,6 @@ from typing import Optional
|
|
|
19
19
|
import click
|
|
20
20
|
|
|
21
21
|
from scriptplan import __version__
|
|
22
|
-
from scriptplan.cli.main import run_scriptplan
|
|
23
22
|
from scriptplan.cli.plan_files import (
|
|
24
23
|
InputFileError,
|
|
25
24
|
create_auto_report_file,
|
|
@@ -28,6 +27,7 @@ from scriptplan.cli.plan_files import (
|
|
|
28
27
|
validate_tjp_file,
|
|
29
28
|
)
|
|
30
29
|
from scriptplan.cli.plan_help import register as register_help_commands
|
|
30
|
+
from scriptplan.cli.programmatic import run_scriptplan
|
|
31
31
|
|
|
32
32
|
# Configure logging
|
|
33
33
|
logger = logging.getLogger(__name__)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Running the report generator from Python, as `plan report` does."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
__all__ = ["run_scriptplan"]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def run_scriptplan(tjp_file: str, output_dir: str | None = None) -> tuple[bool, str | None]:
|
|
12
|
+
"""
|
|
13
|
+
Run ScriptPlan report generation on a .tjp file.
|
|
14
|
+
|
|
15
|
+
This is a simplified interface for programmatic use.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
tjp_file: Path to the .tjp file
|
|
19
|
+
output_dir: Optional output directory for reports (default: current directory)
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
Tuple of (success, error_message)
|
|
23
|
+
"""
|
|
24
|
+
import contextlib
|
|
25
|
+
import io
|
|
26
|
+
|
|
27
|
+
# Capture stderr to get error messages
|
|
28
|
+
stderr_capture = io.StringIO()
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
with contextlib.redirect_stderr(stderr_capture):
|
|
32
|
+
from scriptplan.cli.main import ScriptPlan, create_parser
|
|
33
|
+
|
|
34
|
+
# Create minimal args
|
|
35
|
+
parser = create_parser()
|
|
36
|
+
args_list = [tjp_file]
|
|
37
|
+
if output_dir:
|
|
38
|
+
args_list.extend(["--output-dir", output_dir])
|
|
39
|
+
args = parser.parse_args(args_list)
|
|
40
|
+
|
|
41
|
+
# Suppress logging for programmatic use
|
|
42
|
+
logging.getLogger().setLevel(logging.ERROR)
|
|
43
|
+
|
|
44
|
+
# Run the application
|
|
45
|
+
app = ScriptPlan(args)
|
|
46
|
+
exit_code = app.run()
|
|
47
|
+
|
|
48
|
+
if exit_code != 0:
|
|
49
|
+
error_output = stderr_capture.getvalue()
|
|
50
|
+
return (False, error_output or "Report generation failed")
|
|
51
|
+
|
|
52
|
+
except Exception as e:
|
|
53
|
+
error_output = stderr_capture.getvalue()
|
|
54
|
+
return (False, error_output or str(e))
|
|
55
|
+
|
|
56
|
+
# A schedule that succeeded with warnings still has to show them: a
|
|
57
|
+
# warning nobody sees is a silent change to what the file asked for.
|
|
58
|
+
said = stderr_capture.getvalue()
|
|
59
|
+
if said:
|
|
60
|
+
sys.stderr.write(said if said.endswith("\n") else said + "\n")
|
|
61
|
+
return (True, None)
|