Deferred Directives

run-on-entry blocks only fire when a file is the entry point. A library's deferred block is skipped on import, but executes when loaded directly — enabling self-tests and setup that don't pollute importers.

Files

"""
Demo: Deferred Directives

Scenario: run-on-entry blocks only fire when a file is the entry point.
A library's deferred block is skipped on import, but executes when loaded
directly — enabling self-tests and setup that don't pollute importers.
"""

import logging
import os
import sys

from parseltongue import load_main


def pltg_print(_system, *args):
    print(*[str(a).replace("\\n", "\n") for a in args])
    return True


def print_facts(system):
    for name, fact in system.facts.items():
        print(f"  {name} = {fact.wff}")
    return True


EFFECTS = {
    "print": pltg_print,
    "print-facts": print_facts,
}

if __name__ == "__main__":
    plog = logging.getLogger("parseltongue")
    plog.setLevel(logging.WARNING)
    handler = logging.StreamHandler(sys.stdout)
    handler.setFormatter(logging.Formatter("  [%(levelname)s] %(message)s"))
    plog.addHandler(handler)

    os.chdir(os.path.dirname(__file__))
    load_main("demo.pltg", EFFECTS)
; app.pltg — An application that imports lib.
;
; lib's regular facts load, but its run-on-entry facts are skipped
; because lib is not the main entry point here.

(import (quote lib))

(fact app-name "deferred-demo" :origin "app config")

; app's own run-on-entry — these DO fire because app.pltg is main:
(run-on-entry
    (quote (fact app-initialized true :origin "deferred — app is main")))
; ==========================================================
; Deferred Demo — run-on-entry only fires for the main file
; ==========================================================

(print "============================================================")
(print "Deferred Demo — run-on-entry only fires for the main file")
(print "============================================================")

; --- Scenario: demo.pltg imports app, which imports lib ---
; Neither lib nor app is the entry point — demo.pltg is.
; So ALL run-on-entry blocks in imported modules are skipped.

(print "\n--- Importing app (which imports lib) ---")
(print "  lib's run-on-entry: SKIPPED (not main)")
(print "  app's run-on-entry: SKIPPED (not main)")
(print "  Only regular facts from both modules load.\n")

(import (quote app))

(print "\nFacts loaded (no deferred facts):")
(print-facts)

; --- demo's own run-on-entry — this DOES fire ---
(print "\n--- demo's own run-on-entry ---")
(run-on-entry
    (quote (fact demo-ready true :origin "deferred — demo is main")))

(print "\nFacts after run-on-entry:")
(print-facts)

(print "\n============================================================")
(print "Only demo.pltg is main, so only its run-on-entry fires.")
(print "To see lib's deferred facts, run lib.pltg directly.")
(print "============================================================")
; lib.pltg — A reusable library with deferred directives.
;
; Regular facts load when imported by any file.
; run-on-entry facts only execute when this file is the entry point.

(fact api-version 3 :origin "library constant")
(fact max-retries 5 :origin "library constant")

; These only fire when lib.pltg is run directly (not imported):
(run-on-entry
    (quote (fact standalone-mode true :origin "deferred — only when main"))
    (quote (fact self-test-passed true :origin "deferred — only when main")))

Output

Click "Run in browser" to execute this demo with Pyodide.