Sthenô (Σθεννώ), another of the three Gorgon sisters in Greek mythology and sister to Euryale and Medusa, is traditionally depicted as immortal. Her name conveys “strength” or “might,” underscoring themes of endurance and power. This reference aligns with the library’s focus on robust, reusable components.
Sthenô is a small, cross-platform Swift package that consolidates reusable, context-independent components. It focuses on well-scoped building blocks with clear APIs, thorough DocC documentation, and solid unit test coverage, designed to be adopted piecemeal across apps, frameworks, and tools.
Sthenô depends only on swift-log for structured logging, ensuring consistent and configurable diagnostics across platforms while keeping the overall dependency footprint minimal.
Continuous Integration (CI) is handled through GitHub Actions, which automatically builds, tests, generates documentation, and analyzes the codebase using CodeQL and SonarQube to ensure quality, consistency, and cross-platform reliability.
Documentation is available directly in Xcode and VS Code, and online.
The nested Bridge/ package builds libSthenoBridge, a dynamic
library exposing Sthenô's unit conversions, display formatting and mDNS
Bonjour discovery through a plain C ABI (stheno_bridge_*), so non-Swift
hosts — C# via P/Invoke, Python via ctypes, anything that can load a shared
library — reuse the same code instead of reimplementing it. Build it with
swift build -c release from Bridge/; every returned string is a
caller-owned UTF-8 buffer released with stheno_bridge_string_free.
stheno_bridge_discover browses the marine service types by default (Signal
K over HTTP and WebSocket, NMEA 0183, and the Garmin / Navico / Raymarine /
Furuno vendor types) and returns each endpoint with a ready-to-open URL. It
carries the back-end's own limits: Windows needs Apple's Bonjour service and
Linux needs Avahi, and where no back-end exists — Android — the call reports
ok:false with the reason rather than pretending the network is empty, so
the host can browse with its own platform API instead.
| Platform | CI Status |
|---|---|
These types support practical conversion workflows across common units (for example, °C/°F and kilometers/miles) and marine-oriented units and conventions, including nautical miles, knots, cardinal angles, and Beaufort wind scale mapping.
Many ISO date parsers follow RFC 3339, which is a strict subset of ISO 8601. DateTime implementations are more permissive and accept additional ISO 8601 variants, such as the absence of a time zone or the use of a comma for fractional seconds.
Angle- An angle in degrees normalized to the [0, 360) range.Coordinate- Represents geographical coordinates (latitude and longitude).Distance- Represents a distance value with unit conversions and formatting helpers.DateTime- Represents a date with helpers for parsing and formatting helpers.Speed- Represents a speed value with conversion and formatting helpers.Temperature- Represents a temperature value with conversion and formatting helpers.Volume- Represents a volume (litres, cubic metres, US and imperial gallons) with conversion and formatting helpers — note the two gallons differ (3.785 L vs 4.546 L).Formatted- A display-ready value/unit pair (e.g."12"/"kn"). Every measurement type above exposes a unifiedformatted(as:)taking its ownFormatenum and returning aFormatted, so UIs can render the value large and the unit small without re-parsing strings (Coordinate.formatted(as:)returns a latitude/longitude string pair instead).unitis empty for formats that carry none, such as a cardinal direction.BeaufortScaleandCardinalDirection- Wind-speed force mapping and 16-point compass directions, used by the angle and speed formats.DistanceUnit,SpeedUnit,TemperatureUnit- The unit enumerations behind the conversions.TemperatureUnit.preferred(for:)andVolumeUnit.preferred(for:)resolve the unit the user expects for a locale — honouring the device-level settings on Apple platforms; the temperature setting is independent of both the region and the measurement system (a metric device can read °F, and vice versa).
Geo- Utility functions for geographic calculations (great-circle distance, bearings, etc.).
BonjourDiscovery browses the local network for mDNS/Bonjour services and yields fully resolved endpoints (host, port, path) as an AsyncThrowingStream. A single cross-platform API is implemented on top of the native back-end of each OS:
| Platform | Back-end | Notes |
|---|---|---|
| iOS / macOS / tvOS / watchOS / visionOS / Mac Catalyst | NWBrowser (Network.framework) |
No extra dependency. |
| Linux | Avahi via the dns_sd compatibility C API |
Requires libavahi-compat-libdnssd-dev at runtime; library probed via dlopen so binaries launch even when it is absent. |
| Windows | DnsServiceBrowse / DnsServiceResolve (windns.h) |
Windows 10 1709 and later — no third-party install. |
BonjourDiscovery- Browses mDNS service types with a configurable timeout.DiscoveredEndpoint- Resolved service: scheme, host, port, path, label and a ready-to-use connectionurl.BonjourServiceEntry- Declares a service type to look for (mDNS PTR string, URL scheme, default path, display label).bonjourDefaultServiceTypes- Built-in service catalogue covering marine (Signal K, NMEA 0183, vendor MFDs), file sharing (SMB, AFP, SFTP, FTP, NFS, WebDAV), printing (IPP, LPR), multimedia (AirPlay, Chromecast, DAAP), remote access (SSH, VNC, RDP) and web/IoT (HTTP/S, HomeKit, MQTT).BonjourDiscoveryError- Surfaces back-end failures (e.g. Avahi missing on Linux).
Example — browse the local network for 5 s and connect to the first Signal K server found:
let discovery = BonjourDiscovery()
for try await endpoint in discovery.browse(timeout: 5) {
print(endpoint) // "[signalk-http] raspberrypi — http://192.168.1.20:3000/signalk"
if endpoint.label == "signalk-http" {
// Connect to endpoint.url …
break
}
}The stream finishes automatically at the timeout, can be discarded early (break), and cleans up its underlying browsers/resolvers via onTermination. Pass your own serviceTypes: array to browse non-default services.
DomainResolver- Resolves a hostname to its IP addresses using the system resolver (getaddrinfo).resolve(_:)returns every address;resolveIPv4(_:)/resolveIPv6(_:)return the first match of a given family, and aResolveErrorsurfaces lookup failures. (Apple platforms, Linux and Windows; not available on Android or WASI.)
let addresses = try await DomainResolver.resolve("example.com")
// ["93.184.216.34", "2606:2800:21f:cb07:6820:80da:af6b:8b2c"]downloadURLasString- Downloads the contents of the given URL and decodes it as a UTF-8String. (Not implemented on WASI)
Uses only the pure toolchain, not SwiftNIO, even on Linux or Windows.
Color- Cross-platform color support with strict HTML hex parsing/formatting and native bridging (Color,UIColor,NSColor) when available.
cleanHtml(from:)- Removes all known HTML tags from a string and decodes common HTML character entities to their Unicode equivalents.
Example:
let result = cleanHtml(from: "<body><h1>Un œil éveillé</h1>& exemple à <10€></body>")
print(result)Output:
Un œil éveillé & exemple à <10€>
-
Extension to
Bundleto access versioning information and bundle name from the app’s Info.plist. -
isRunningInPreviews- Indicates whether code is running under Xcode previews. -
isTestFlight- Indicates whether the app is running a TestFlight build (a sandbox App Store receipt). -
Throttled- A property wrapper that throttles updates to its wrapped value. -
interceptingStdOut(to:encoding:body:)- Captures text written to standard output into aTextOutputStreamwhile a closure runs (Apple platforms only).