///|
/// Normalizes constructor inputs and supports dependency injection in tests.
fn require_supported_platform(
platform : Platform,
) -> Result[Unit, AutoLaunchError] {
if backend_for_platform(platform) == BackendKind::UnsupportedBackend {
Err(AutoLaunchError::UnsupportedPlatform(platform))
} else {
Ok(())
}
}
///|
/// Trims caller input and rejects empty values with a typed validation error.
fn require_non_empty_text(
text : String,
empty_error : AutoLaunchError,
) -> Result[String, AutoLaunchError] {
let trimmed = trim_text(text)
if trimmed.is_empty() {
Err(empty_error)
} else {
Ok(trimmed)
}
}
///|
/// Resolves the executable path from explicit input or the runtime probe.
fn resolve_executable_path(
path : String?,
get_current_executable_path~ : () -> Result[String, AutoLaunchError],
) -> Result[String, AutoLaunchError] {
match path {
Some(explicit_path) =>
require_non_empty_text(
explicit_path,
AutoLaunchError::EmptyExecutablePath,
)
None => get_current_executable_path()
}
}
///|
/// Normalizes constructor inputs and supports dependency injection in tests.
fn new_with(
platform : Platform,
name : String,
path : String?,
launch_in_background : Bool,
background_arg : String,
extra_arguments : Array[String],
identifier : String?,
get_current_executable_path~ : () -> Result[String, AutoLaunchError],
) -> Result[AutoLaunch, AutoLaunchError] {
match require_supported_platform(platform) {
Ok(_) => ()
Err(error) => return Err(error)
}
let trimmed_name = match
require_non_empty_text(name, AutoLaunchError::EmptyName) {
Ok(value) => value
Err(error) => return Err(error)
}
let trimmed_background_arg = trim_text(background_arg)
if launch_in_background && trimmed_background_arg.is_empty() {
return Err(AutoLaunchError::EmptyBackgroundArgument)
}
let resolved_path = match
resolve_executable_path(path, get_current_executable_path~) {
Ok(value) => value
Err(error) => return Err(error)
}
if !is_absolute_path(platform, resolved_path) {
return Err(AutoLaunchError::RelativeExecutablePath(resolved_path))
}
Ok(AutoLaunch::{
config: {
name: trimmed_name,
app_path: resolved_path,
identifier: default_identifier(trimmed_name, identifier),
launch_in_background,
background_arg: trimmed_background_arg,
extra_arguments,
},
})
}
///|
/// Create an auto-launch configuration for the current platform.
///
/// `name` is the human-readable application name shown in generated desktop
/// entries. `path` defaults to the current executable path when omitted.
///
/// When `launch_in_background` is `true`, the package appends
/// `background_arg` before any `extra_arguments`. This mirrors the common
/// `--hidden` startup convention used by desktop applications.
///
/// `identifier` controls the stable registry value or file name used by the
/// backend. When omitted, it is derived from `name`.
///
/// Returns `Err(AutoLaunchError)` when validation fails, when the current
/// platform is unsupported, or when the executable path cannot be discovered.
///
/// The resulting value is reusable: you can keep it around and call
/// `enable()`, `disable()`, and `is_enabled()` multiple times.
///
/// # Example
/// ```mbt nocheck
/// let launcher = @auto_launch.new(
/// "MoonBit Demo",
/// path="/usr/bin/moonbit",
/// launch_in_background=true,
/// extra_arguments=["--serve"],
/// )
/// ```
pub fn new(
name : String,
path? : String,
launch_in_background? : Bool = false,
background_arg? : String = "--hidden",
extra_arguments? : Array[String] = [],
identifier? : String,
) -> Result[AutoLaunch, AutoLaunchError] {
new_with(
current_platform(),
name,
path,
launch_in_background,
background_arg,
extra_arguments,
identifier,
get_current_executable_path=current_executable_path_result,
)
}
///|
/// Returns the configured human-readable application name.
///
/// This is the display name used in generated desktop-entry style metadata. It
/// may contain spaces and punctuation and is independent from the backend
/// identifier used for file names or registry keys.
pub fn AutoLaunch::name(self : AutoLaunch) -> String {
self.config.name
}
///|
/// Returns the resolved absolute executable path that will be launched.
///
/// The value returned here is always the normalized absolute path that passed
/// constructor validation, either from the explicit `path` argument or from the
/// current executable path discovered by the native backend.
pub fn AutoLaunch::path(self : AutoLaunch) -> String {
self.config.app_path
}
///|
/// Returns the stable backend identifier used for the registry value or file
/// name.
///
/// This value is derived from `name` unless the caller provided `identifier`
/// explicitly. It is sanitized for backend-safe usage and is what ties
/// `enable()`, `disable()`, and `is_enabled()` to the same platform entry.
pub fn AutoLaunch::identifier(self : AutoLaunch) -> String {
self.config.identifier
}
///|
/// Enable auto-launch for this configuration on the current platform.
///
/// On supported platforms this creates or updates the corresponding startup
/// entry:
///
/// - Windows: writes the current-user `Run` registry value
/// - macOS: writes a LaunchAgent plist under `~/Library/LaunchAgents`
/// - Linux: writes an XDG autostart desktop entry under
/// `~/.config/autostart`
///
/// Returns `Ok(())` when the entry was written successfully.
pub fn AutoLaunch::enable(self : AutoLaunch) -> Result[Unit, AutoLaunchError] {
enable_with(
self,
current_platform(),
home_directory_result,
windows_set_run_entry_result,
write_text_file_result,
)
}
///|
/// Disable auto-launch for this configuration on the current platform.
///
/// Missing entries are treated as already-disabled and therefore do not produce
/// an error in the native backend.
pub fn AutoLaunch::disable(self : AutoLaunch) -> Result[Unit, AutoLaunchError] {
disable_with(
self,
current_platform(),
home_directory_result,
windows_delete_run_entry_result,
remove_file_result,
)
}
///|
/// Check whether the auto-launch entry currently exists for this configuration.
///
/// This is an existence check against the backend entry identified by
/// `identifier()`. It does not attempt to prove that the entry still contains
/// the exact expected command line beyond the backend's own lookup semantics.
pub fn AutoLaunch::is_enabled(
self : AutoLaunch,
) -> Result[Bool, AutoLaunchError] {
is_enabled_with(
self,
current_platform(),
home_directory_result,
windows_run_entry_exists_result,
file_exists_result,
)
}