1-module(rebar_prv_unlock).
2
3-behaviour(provider).
4
5-export([init/1,
6         do/1,
7         format_error/1]).
8
9-include("rebar.hrl").
10-include_lib("providers/include/providers.hrl").
11
12-define(PROVIDER, unlock).
13-define(DEPS, []).
14
15%% ===================================================================
16%% Public API
17%% ===================================================================
18
19-spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
20init(State) ->
21    State1 = rebar_state:add_provider(
22        State,
23        providers:create([{name, ?PROVIDER},
24                          {module, ?MODULE},
25                          {bare, true},
26                          {deps, ?DEPS},
27                          {example, ""},
28                          {short_desc, "Unlock dependencies."},
29                          {desc, "Unlock project dependencies. Mentioning no application "
30                                 "will unlock all dependencies. To unlock specific dependencies, "
31                                 "their name can be listed in the command."},
32                          {opts, [
33                            {package, undefined, undefined, string,
34                             "List of packages to unlock. If not specified, all dependencies are unlocked."}
35                          ]}
36                         ])
37    ),
38    {ok, State1}.
39
40do(State) ->
41    Dir = rebar_state:dir(State),
42    LockFile = filename:join(Dir, ?LOCK_FILE),
43    case file:consult(LockFile) of
44        {error, enoent} ->
45            %% Our work is done.
46            {ok, State};
47        {error, Reason} ->
48            ?PRV_ERROR({file,Reason});
49        {ok, _} ->
50            Locks = rebar_config:consult_lock_file(LockFile),
51            {ok, NewLocks} = handle_unlocks(State, Locks, LockFile),
52            {ok, rebar_state:set(State, {locks, default}, NewLocks)}
53    end.
54
55-spec format_error(any()) -> iolist().
56format_error({file, Reason}) ->
57    io_lib:format("Lock file editing failed for reason ~p", [Reason]);
58format_error(unknown_lock_format) ->
59    "Lock file format unknown";
60format_error(Reason) ->
61    io_lib:format("~p", [Reason]).
62
63handle_unlocks(State, Locks, LockFile) ->
64    {Args, _} = rebar_state:command_parsed_args(State),
65    Names = parse_names(rebar_utils:to_binary(proplists:get_value(package, Args, <<"">>))),
66    case [Lock || Lock = {Name, _, _} <- Locks, not lists:member(Name, Names)] of
67        [] ->
68            file:delete(LockFile),
69            {ok, []};
70        _ when Names =:= [] -> % implicitly all locks
71            file:delete(LockFile),
72            {ok, []};
73        NewLocks ->
74            rebar_config:write_lock_file(LockFile, NewLocks),
75            {ok, NewLocks}
76    end.
77
78parse_names(Bin) ->
79    case lists:usort(re:split(Bin, <<" *, *">>, [trim, unicode])) of
80        [<<"">>] -> []; % nothing submitted
81        Other -> Other
82    end.
83