aboutsummaryrefslogtreecommitdiff
path: root/src/er_xdg_pipe_menu_desktop_entry.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/er_xdg_pipe_menu_desktop_entry.erl')
-rw-r--r--src/er_xdg_pipe_menu_desktop_entry.erl141
1 files changed, 0 insertions, 141 deletions
diff --git a/src/er_xdg_pipe_menu_desktop_entry.erl b/src/er_xdg_pipe_menu_desktop_entry.erl
deleted file mode 100644
index 9a71013..0000000
--- a/src/er_xdg_pipe_menu_desktop_entry.erl
+++ /dev/null
@@ -1,141 +0,0 @@
-%%%-------------------------------------------------------------------
-%% @doc Parser for XDG .desktop files.
-%%
-%% Reads the `[Desktop Entry]' group of a desktop entry file and
-%% extracts the fields needed to build a menu item: `Name', `Exec'
-%% and (optionally) `Icon' and `Categories'. Other groups (e.g.
-%% `[Desktop Action ...]') and localized keys (e.g. `Name[fr]') are
-%% ignored.
-%% @end
-%%%-------------------------------------------------------------------
-
--module(er_xdg_pipe_menu_desktop_entry).
-
--include("er_xdg_pipe_menu.hrl").
-
--export([parse_file/1, parse/1]).
-
--define(GROUP, <<"Desktop Entry">>).
--define(WANTED_KEYS, [
- <<"Name">>, <<"Exec">>, <<"Icon">>, <<"Categories">>, <<"NoDisplay">>, <<"Hidden">>, <<"Type">>
-]).
--define(FIELD_CODES, "fFuUdDnNickvm").
-
--spec parse_file(file:filename_all()) -> {ok, #desktop_entry{}} | {error, term()}.
-parse_file(Path) ->
- case file:read_file(Path) of
- {ok, Bin} -> parse(Bin);
- {error, Reason} -> {error, Reason}
- end.
-
--spec parse(binary()) -> {ok, #desktop_entry{}} | {error, missing_name | missing_exec}.
-parse(Bin) ->
- to_entry(scan(Bin)).
-
-%% Fold every line into a map of the fields we care about, tracking
-%% which `[Group]' we're currently inside.
-scan(Bin) ->
- Lines = binary:split(Bin, [<<"\r\n">>, <<"\n">>], [global]),
- {_Group, Fields} = lists:foldl(fun scan_line/2, {undefined, #{}}, Lines),
- Fields.
-
-scan_line(Line, {Group, Fields}) ->
- case classify(string:trim(Line)) of
- skip ->
- {Group, Fields};
- {section, Name} ->
- {Name, Fields};
- {kv, Key, Value} ->
- case Group =:= ?GROUP andalso lists:member(Key, ?WANTED_KEYS) of
- true -> {Group, maps:put(Key, unescape(Value), Fields)};
- false -> {Group, Fields}
- end
- end.
-
-classify(<<>>) ->
- skip;
-classify(<<"#", _/binary>>) ->
- skip;
-classify(<<"[", Rest/binary>>) ->
- Size = byte_size(Rest) - 1,
- case Rest of
- <<Name:Size/binary, "]">> -> {section, string:trim(Name)};
- _ -> skip
- end;
-classify(Line) ->
- case binary:split(Line, <<"=">>) of
- [Key, Value] -> {kv, string:trim(Key), string:trim(Value)};
- [_] -> skip
- end.
-
-%% Desktop entry escape sequences: \\ \s \n \t \r
-unescape(Bin) ->
- unescape(Bin, <<>>).
-
-unescape(<<>>, Acc) ->
- Acc;
-unescape(<<$\\, $\\, Rest/binary>>, Acc) ->
- unescape(Rest, <<Acc/binary, $\\>>);
-unescape(<<$\\, $s, Rest/binary>>, Acc) ->
- unescape(Rest, <<Acc/binary, $\s>>);
-unescape(<<$\\, $n, Rest/binary>>, Acc) ->
- unescape(Rest, <<Acc/binary, $\n>>);
-unescape(<<$\\, $t, Rest/binary>>, Acc) ->
- unescape(Rest, <<Acc/binary, $\t>>);
-unescape(<<$\\, $r, Rest/binary>>, Acc) ->
- unescape(Rest, <<Acc/binary, $\r>>);
-unescape(<<C, Rest/binary>>, Acc) ->
- unescape(Rest, <<Acc/binary, C>>).
-
-to_entry(Fields) ->
- case maps:find(<<"Name">>, Fields) of
- error ->
- {error, missing_name};
- {ok, Name} ->
- case maps:find(<<"Exec">>, Fields) of
- error ->
- {error, missing_exec};
- {ok, Exec} ->
- {ok, #desktop_entry{
- name = Name,
- exec = strip_field_codes(Exec),
- icon = maps:get(<<"Icon">>, Fields, undefined),
- categories = parse_categories(maps:get(<<"Categories">>, Fields, undefined)),
- hidden = is_hidden(Fields)
- }}
- end
- end.
-
-%% `Categories' is a `;'-separated list, conventionally with a
-%% trailing separator (e.g. `Utility;Development;'); empty segments
-%% from that trailing separator (or repeated ones) are dropped.
-parse_categories(undefined) ->
- [];
-parse_categories(Bin) ->
- [C || C <- binary:split(Bin, <<";">>, [global]), C =/= <<>>].
-
-%% Niche variations (e.g. a Type-less override dropped into
-%% ~/.local/share/applications) are left to be special-cased there
-%% rather than accounted for here.
-is_hidden(Fields) ->
- maps:get(<<"NoDisplay">>, Fields, <<"false">>) =:= <<"true">> orelse
- maps:get(<<"Hidden">>, Fields, <<"false">>) =:= <<"true">> orelse
- case maps:find(<<"Type">>, Fields) of
- {ok, Type} -> Type =/= <<"Application">>;
- error -> false
- end.
-
-%% Field codes (%f, %u, %i, ...) are expanded by launchers that pass
-%% files/URIs on the command line; a pipemenu has none to pass, so
-%% each code is dropped along with the whitespace around it. `%%' is
-%% then unescaped to a literal `%'.
-strip_field_codes(Exec) ->
- Tokens = binary:split(Exec, <<" ">>, [global]),
- Kept = [T || T <- Tokens, T =/= <<>>, not is_field_code(T)],
- Joined = iolist_to_binary(lists:join(<<" ">>, Kept)),
- binary:replace(Joined, <<"%%">>, <<"%">>, [global]).
-
-is_field_code(<<"%", C>>) ->
- lists:member(C, ?FIELD_CODES);
-is_field_code(_) ->
- false.