-module(tinman_ffi). -export([platform/0, is_windows/0, arch/0, version/0, hostname/0, endianness/0, used_memory/0, cpu_count/0, available_parallelism/0, runtime/0, runtime_version/0, is_online/0, is_tty/0, is_test/0, process_id/0, libc_type/0, read_file/1, file_exists/1, os_cmd/1, env/1]). platform() -> case os:type() of {unix, Unix} -> atom_to_binary(Unix); {Family, _} -> atom_to_binary(Family) end. is_windows() -> case os:type() of {win32, _} -> true; _ -> false end. arch() -> [Arch | _] = string:split( erlang:system_info(system_architecture), "-"), list_to_binary(Arch). version() -> case os:version() of {Major, Minor, Patch} -> {ok, {version, Major, Minor, Patch}}; _ -> {error, nil} end. hostname() -> {ok, Hostname} = inet:gethostname(), list_to_binary(Hostname). endianness() -> case erlang:system_info(endian) of big -> big_endian; little -> little_endian end. used_memory() -> erlang:memory(total). cpu_count() -> case erlang:system_info(logical_processors) of unknown -> {error, nil}; Count -> {ok, Count} end. available_parallelism() -> case erlang:system_info(logical_processors_available) of unknown -> erlang:system_info(schedulers_online); Count -> min(Count, erlang:system_info(schedulers_online)) end. runtime() -> list_to_binary(erlang:system_info(machine)). runtime_version() -> list_to_binary(erlang:system_info(version)). is_online() -> case inet:getifaddrs() of {ok, Interfaces} -> has_non_internal_interface(Interfaces); _ -> false end. has_non_internal_interface([]) -> false; has_non_internal_interface([{_Name, Props} | Rest]) -> case is_interface_online(Props) of true -> true; false -> has_non_internal_interface(Rest) end. is_interface_online(Props) -> Flags = proplists:get_value(flags, Props, []), Internal = lists:member(loopback, Flags), HwAddr = proplists:get_value(hwaddr, Props), HasValidMac = HwAddr =/= undefined andalso HwAddr =/= [0, 0, 0, 0, 0, 0], not Internal andalso HasValidMac. is_tty() -> case io:columns() of {ok, _} -> % Check io:getopts for stdin, stdout, and terminal flags Opts = io:getopts(), HasStdin = proplists:get_value(stdin, Opts, false), HasStdout = proplists:get_value(stdout, Opts, false), HasStdin and HasStdout; {error, _} -> false end. is_test() -> try throw(check_stack) catch _:_:Stack -> case lists:reverse(Stack) of % Pattern 1: application_master -> *.start: from within app start, need to check the command line [{application_master, _, _, _}, {_UserModule, start, _, _} | _] -> OsPid = os:getpid(), PsCmd = "ps -p " ++ OsPid ++ " -o command=", CommandLine = os:cmd(PsCmd), string:str(CommandLine, "_test") > 0; % Pattern 2: *@@main.run_module -> *_test.main: directly in test start module [{MainModule, run_module, _, _}, {TestModule, main, _, _} | _] -> gleam@string:ends_with(atom_to_binary(MainModule), <<"@@main">>) andalso gleam@string:ends_with(atom_to_binary(TestModule), <<"_test">>); % Pattern 3: eunit_proc -> ... (called from within a test function) [{eunit_proc, _, _, _} | _] -> true; _ -> false end end. %% -------------------- Process Information -------------------- process_id() -> list_to_integer(os:getpid()). libc_type() -> case os:type() of {unix, linux} -> Arch = erlang:system_info(system_architecture), Parts = string:split(Arch, "-", all), case lists:member("musl", Parts) of true -> {ok, musl}; false -> {ok, glibc} end; _ -> {error, nil} end. read_file(Path) -> case file:read_file(Path) of {ok, Binary} -> {ok, Binary}; {error, _} -> {error, nil} end. file_exists(Path) -> case filelib:is_file(binary_to_list(Path)) of true -> true; false -> false end. os_cmd(Command) -> Result = os:cmd(binary_to_list(Command)), list_to_binary(Result). env(Name) -> case os:getenv(binary_to_list(Name)) of false -> <<>>; Value -> list_to_binary(Value) end.