From 7db20cd2ac2c8b8774a8ae02a7b233541666c865 Mon Sep 17 00:00:00 2001 From: "Christopher R. Nelson" Date: Mon, 20 Jul 2026 22:56:01 -0400 Subject: Add XDG applications directory scanner Resolves the ordered list of "applications" directories to search per the XDG Base Directory Specification: XDG_DATA_HOME (default ~/.local/share) first, then XDG_DATA_DIRS (default /usr/local/share, /usr/share). Each variable falls back to its default independently when unset or empty, so a Flatpak-style XDG_DATA_DIRS override is honored in place of the default rather than merged with it. find_desktop_files/0,1 recursively collects .desktop files from those dirs, skipping ones that don't exist. Co-Authored-By: Claude Sonnet 5 --- test/er_xdg_pipe_menu_scanner_tests.erl | 144 ++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 test/er_xdg_pipe_menu_scanner_tests.erl (limited to 'test') diff --git a/test/er_xdg_pipe_menu_scanner_tests.erl b/test/er_xdg_pipe_menu_scanner_tests.erl new file mode 100644 index 0000000..3f93496 --- /dev/null +++ b/test/er_xdg_pipe_menu_scanner_tests.erl @@ -0,0 +1,144 @@ +-module(er_xdg_pipe_menu_scanner_tests). + +-include_lib("eunit/include/eunit.hrl"). + +data_dirs_defaults_test() -> + with_env( + [{"XDG_DATA_HOME", false}, {"XDG_DATA_DIRS", false}, {"HOME", "/home/testuser"}], + fun() -> + ?assertEqual( + [ + "/home/testuser/.local/share/applications", + "/usr/local/share/applications", + "/usr/share/applications" + ], + er_xdg_pipe_menu_scanner:data_dirs() + ) + end + ). + +data_dirs_honors_xdg_data_home_test() -> + with_env( + [{"XDG_DATA_HOME", "/custom/data"}, {"XDG_DATA_DIRS", false}, {"HOME", "/home/testuser"}], + fun() -> + ?assertEqual( + [ + "/custom/data/applications", + "/usr/local/share/applications", + "/usr/share/applications" + ], + er_xdg_pipe_menu_scanner:data_dirs() + ) + end + ). + +data_dirs_honors_xdg_data_dirs_test() -> + with_env( + [ + {"XDG_DATA_HOME", false}, + {"XDG_DATA_DIRS", "/opt/flatpak/share:/var/lib/flatpak/exports/share"}, + {"HOME", "/home/testuser"} + ], + fun() -> + ?assertEqual( + [ + "/home/testuser/.local/share/applications", + "/opt/flatpak/share/applications", + "/var/lib/flatpak/exports/share/applications" + ], + er_xdg_pipe_menu_scanner:data_dirs() + ) + end + ). + +data_dirs_treats_empty_as_unset_test() -> + with_env( + [{"XDG_DATA_HOME", ""}, {"XDG_DATA_DIRS", ""}, {"HOME", "/home/testuser"}], + fun() -> + ?assertEqual( + [ + "/home/testuser/.local/share/applications", + "/usr/local/share/applications", + "/usr/share/applications" + ], + er_xdg_pipe_menu_scanner:data_dirs() + ) + end + ). + +data_dirs_ignores_empty_segments_in_xdg_data_dirs_test() -> + with_env( + [{"XDG_DATA_HOME", false}, {"XDG_DATA_DIRS", "/a::/b:"}, {"HOME", "/home/testuser"}], + fun() -> + ?assertEqual( + ["/home/testuser/.local/share/applications", "/a/applications", "/b/applications"], + er_xdg_pipe_menu_scanner:data_dirs() + ) + end + ). + +data_dirs_skips_data_home_without_xdg_data_home_or_home_test() -> + with_env( + [{"XDG_DATA_HOME", false}, {"XDG_DATA_DIRS", false}, {"HOME", false}], + fun() -> + ?assertEqual( + ["/usr/local/share/applications", "/usr/share/applications"], + er_xdg_pipe_menu_scanner:data_dirs() + ) + end + ). + +find_desktop_files_recurses_and_skips_missing_dirs_test() -> + Root = mk_tmp_dir(), + SubDir = filename:join(Root, "kde4"), + ok = file:make_dir(SubDir), + ok = file:write_file(filename:join(Root, "top.desktop"), <<"[Desktop Entry]\n">>), + ok = file:write_file(filename:join(SubDir, "nested.desktop"), <<"[Desktop Entry]\n">>), + ok = file:write_file(filename:join(Root, "not-a-desktop-file.txt"), <<"ignore me">>), + Missing = filename:join(Root, "does-not-exist"), + Found = er_xdg_pipe_menu_scanner:find_desktop_files([Root, Missing]), + ok = rm_rf(Root), + ?assertEqual( + lists:sort([filename:join(Root, "top.desktop"), filename:join(SubDir, "nested.desktop")]), + lists:sort(Found) + ). + +find_desktop_files_empty_for_no_dirs_test() -> + ?assertEqual([], er_xdg_pipe_menu_scanner:find_desktop_files([])). + +%% -- helpers ---------------------------------------------------------------- + +with_env(Vars, Fun) -> + Saved = [{Name, os:getenv(Name)} || {Name, _} <- Vars], + try + lists:foreach(fun({Name, Value}) -> set_env(Name, Value) end, Vars), + Fun() + after + lists:foreach(fun({Name, Value}) -> set_env(Name, Value) end, Saved) + end. + +set_env(Name, false) -> os:unsetenv(Name); +set_env(Name, Value) -> os:putenv(Name, Value). + +mk_tmp_dir() -> + Path = filename:join( + test_tmp_dir(), "eunit-scanner-" ++ integer_to_list(erlang:unique_integer([positive])) + ), + ok = file:make_dir(Path), + Path. + +test_tmp_dir() -> + case os:getenv("TMPDIR") of + false -> "/tmp"; + Dir -> Dir + end. + +rm_rf(Path) -> + case filelib:is_dir(Path) of + true -> + {ok, Entries} = file:list_dir(Path), + lists:foreach(fun(E) -> rm_rf(filename:join(Path, E)) end, Entries), + file:del_dir(Path); + false -> + file:delete(Path) + end. -- cgit v1.2.3