diff --git a/.github/workflows/cpp-python-build.yml b/.github/workflows/cpp-python-build.yml index 1056abb7b8..5a49357630 100644 --- a/.github/workflows/cpp-python-build.yml +++ b/.github/workflows/cpp-python-build.yml @@ -497,7 +497,7 @@ jobs: run: ctest --parallel $(sysctl -n hw.logicalcpu) --output-on-failure - name: Build the source package for Python run: | - python python/pyxstubgen.py --pyxfile=build/python/_jsbsim.pyx --output=python/_jsbsim.pyi + python python/pyxstubgen.py --pyxfile=build/python/_jsbsim.pyx --output=python/__init__.pyi cp build/python/pyproject.toml . python -m build --sdist - name: Check reset for real time execution diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 75c2345f64..3d3f7f884f 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -58,7 +58,7 @@ target_link_libraries(_jsbsim PRIVATE libJSBSim) set_target_properties(_jsbsim PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${JSBSIM_TEST_PACKAGE_DIR}) install(TARGETS _jsbsim DESTINATION jsbsim COMPONENT wheel) install(FILES ${PROJECT_SOURCE_DIR}/python/__init__.py DESTINATION jsbsim COMPONENT wheel) -install(FILES ${PROJECT_SOURCE_DIR}/python/_jsbsim.pyi DESTINATION jsbsim COMPONENT wheel) +install(FILES ${PROJECT_SOURCE_DIR}/python/__init__.pyi DESTINATION jsbsim COMPONENT wheel) install(PROGRAMS ${PROJECT_SOURCE_DIR}/python/JSBSim.py DESTINATION jsbsim RENAME script.py COMPONENT wheel) diff --git a/python/__init__.py b/python/__init__.py index fe3e5f9a43..f70d7b0c8b 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -1,8 +1,8 @@ -from . import _jsbsim from ._jsbsim import ( __version__, Attribute, BaseError, + DefaultLogger, FGAerodynamics, FGAircraft, FGAtmosphere, @@ -26,4 +26,5 @@ ePressure, eTemperature, get_default_root_dir, + set_logger, ) diff --git a/python/jsbsim.pyx.in b/python/jsbsim.pyx.in index 98a5874e04..2869e227f0 100644 --- a/python/jsbsim.pyx.in +++ b/python/jsbsim.pyx.in @@ -154,14 +154,14 @@ LogLevel_PyClass = LogLevel LogFormat_PyClass = LogFormat -def inherit_docstring(cls): +def _inherit_docstring_from_super_class(cls): for name, func in cls.__dict__.items(): if not func.__doc__: func.__doc__ = getattr(super(cls, cls), name).__doc__ return cls -@inherit_docstring +@_inherit_docstring_from_super_class class DefaultLogger(FGLogger): """Default logger: print messages to stdout without formatting.""" def file_location(self, filename: str, line: int) -> None: @@ -270,7 +270,7 @@ cdef class FGPropertyNode: self.__intercept_invalid_pointer() return GetFullyQualifiedName(self.thisptr.ptr()).decode() - def get_node(self, path: str, create: bool = False) -> Optional[SGPropertyNode]: + def get_node(self, path: str, create: bool = False) -> Optional[FGPropertyNode]: """Get a pointer to another node by relative path. :param path: The relative path from the node. @@ -325,7 +325,7 @@ cdef class FGPropertyManager: if not self.thisptr: raise MemoryError() - def get_node(self, path: Optional[str] = None, create: bool = False) -> Optional[SGPropertyNode]: + def get_node(self, path: Optional[str] = None, create: bool = False) -> Optional[FGPropertyNode]: """@Dox(JSBSim::FGPropertyManager::GetNode)""" node = FGPropertyNode() if path is None: diff --git a/python/pyxstubgen.py b/python/pyxstubgen.py index bc3ca1643a..99eb7cb62f 100644 --- a/python/pyxstubgen.py +++ b/python/pyxstubgen.py @@ -2,7 +2,7 @@ # # pyxstubgen.py # -# Automatic generation of the JSBSim stub file `_jsbsim.pyi` from Cython's pyx file. +# Automatic generation of the JSBSim stub file `*.pyi` from Cython's pyx file. # # Copyright (c) 2025 Bertrand Coconnier # @@ -273,9 +273,9 @@ def python__string(self, tree: Tree) -> str: def python__decorator(self, tree: Tree) -> str: assert len(tree.children) == 2 assert tree.children[1] is None - self.output.write( - f"\n{self.TAB_SPACES*self.indent}@{dotted_name(tree.children[0])}" - ) + decorator_name = dotted_name(tree.children[0]) + if decorator_name[0] != "_": + self.output.write(f"\n{self.TAB_SPACES*self.indent}@{decorator_name}") def funcdef(self, tree: Tree) -> None: func_name: str = "" @@ -293,6 +293,8 @@ def funcdef(self, tree: Tree) -> None: func_name = rule_name(child) if func_name in ("__cinit__", "__dealloc__"): return + if func_name[0] == "_" and func_name[1] != '_': + return elif child_type.value == "cparameters": # Get the function parameters parameters: List[str] = [] for cparameter in child.children: diff --git a/src/input_output/FGLog.h b/src/input_output/FGLog.h index 758841ec24..dc757c94c6 100644 --- a/src/input_output/FGLog.h +++ b/src/input_output/FGLog.h @@ -94,14 +94,47 @@ CLASS DOCUMENTATION CLASS DECLARATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/ +/** + * Logging backend interface. + * + * JSBSim routes each log record to an `FGLogger` instance instead of writing + * directly to stdout/stderr. Applications can keep the default `FGLogConsole` + * backend, or provide their own subclass and register it through + * `SetLogger(FGLogger_ptr)`. + * + * A single log record follows this lifecycle: + * 1. `SetLevel()` starts a new log record and gives the severity. + * 2. `FileLocation()` may be called (typically for XML-related messages), + * immediately after `SetLevel()` and before message content. + * 3. `Message()` receives the textual payload. It can be invoked multiple times + * for one logical record because JSBSim may build a message in fragments. + * 4. `Format()` communicates formatting intent (colors, emphasis, reset) for + * the subsequent output. Implementations may ignore it if formatting is not + * applicable. + * 5. `Flush()` ends the current log record and is the right place to finalize + * and emit the record (for example: prefixing, buffering, forwarding to a + * UI, or forcing output synchronization). + * + * Implementations are expected to keep enough internal state between these + * callbacks to assemble one coherent log record. + * + * \see SetLogger + * \see GetLogger + */ class JSBSIM_API FGLogger { public: + /// Virtual destructor for polymorphic use. virtual ~FGLogger() {} + /// Starts a new log record and provides its severity level. virtual void SetLevel(LogLevel level) { log_level = level;} + /// Optionally provides source filename and line for contextual diagnostics. virtual void FileLocation(const std::string& filename, int line) {} + /// Appends message text. May be called multiple times per log record. virtual void Message(const std::string& message) = 0; + /// Applies a formatting hint to subsequent output. virtual void Format(LogFormat format) {} + /// Ends the current log record and commits any buffered output. virtual void Flush(void) {} protected: LogLevel log_level = LogLevel::BULK; @@ -109,7 +142,22 @@ class JSBSIM_API FGLogger using FGLogger_ptr = std::shared_ptr; +/** + * Sets the active logger for the current thread. + * + * The logger is stored in thread-local storage: all JSBSim instances running + * in this thread share this same `FGLogger` instance, while other threads keep + * their own independent logger instance. + * + * \see GetLogger + */ JSBSIM_API void SetLogger(FGLogger_ptr logger); + +/** + * Returns the active logger for the current thread. + * + * \see SetLogger + */ JSBSIM_API FGLogger_ptr GetLogger(void); class JSBSIM_API FGLogging