%% ------------------------------------------------------------------- %% %% @doc Rebar3 plugin for cleaning compiled diameter files. %% %% This module provides cleanup functionality for files generated by the %% diameter compiler plugin. It removes generated .erl and .hrl files, %% but preserves the original .dia source files. %% %% The cleaner automatically discovers which files were generated based on %% the .dia files present in the project and removes only those files that %% would be regenerated by compilation. %% %% == Usage == %% %% The plugin is typically invoked automatically via provider hooks: %% ``` %% rebar3 diameter clean %% ''' %% %% @author Carlos Eduardo de Paula %% @since 1.0.0 %% @end %% %% ------------------------------------------------------------------- -module(clean_diameter). -export([init/1, do/1, format_error/1]). -define(PROVIDER, clean). -define(DEPS, [{default, app_discovery}]). %%%=================================================================== %%% API %%%=================================================================== %% @doc Initialize the diameter clean provider. %% %% Sets up the rebar3 provider for cleaning diameter-generated files. %% This provider removes .erl and .hrl files that were generated from %% .dia sources during compilation. %% %% @param State The current rebar3 state %% @returns {ok, UpdatedState} with the provider registered %% @end -spec init(rebar_state:t()) -> {ok, rebar_state:t()}. init(State) -> Provider = %% The 'user friendly' name of the task providers:create([ {name, ?PROVIDER}, %% The module implementation of the task {module, ?MODULE}, {bare, %% The task can be run by the user, always true true}, %% The list of dependencies {deps, ?DEPS}, %% How to use the plugin {example, "rebar diameter clean"}, %% list of options understood by the plugin {opts, []}, {short_desc, "Clean compiled diameter files."}, {desc, ""}, {namespace, diameter} ]), {ok, rebar_state:add_provider(State, Provider)}. %% @doc Execute the diameter cleanup process. %% %% This function performs the cleanup work by: %%
    %%
  1. Discovering all .dia files in the project
  2. %%
  3. Determining which .erl and .hrl files they generated
  4. %%
  5. Removing those generated files
  6. %%
%% %% @param State The current rebar3 state %% @returns {ok, State} after cleanup is complete %% @end -spec do(rebar_state:t()) -> {ok, rebar_state:t()}. do(State) -> rebar_api:info("Cleaning compiled diameter files...", []), Apps = case rebar_state:current_app(State) of undefined -> rebar_state:project_apps(State); AppInfo -> [AppInfo] end, lists:foreach(fun(App) -> clean(State, App) end, Apps), {ok, State}. %% @doc Format error messages for user display. %% %% @param Reason The error term to format %% @returns Formatted error message as iolist %% @end -spec format_error(any()) -> iolist(). format_error(Reason) -> io_lib:format("~p", [Reason]). %% @doc Clean generated files for a specific application. %% %% Discovers .dia files and removes their corresponding generated .erl and .hrl files. %% Only removes files that can be regenerated from .dia sources. %% %% @param State Rebar3 state containing configuration %% @param _AppFile Application info (directory paths extracted from this) %% @returns ok after cleanup %% @private clean(State, _AppFile) -> AppDir = rebar_app_info:dir(_AppFile), EbinDir = rebar_app_info:ebin_dir(_AppFile), DiaOpts = rebar_state:get(State, dia_opts, []), IncludeEbin = proplists:get_value(include, DiaOpts, []), code:add_pathsz([EbinDir | IncludeEbin]), GeneratedFiles = dia_generated_files(AppDir, "dia", "src", "include", DiaOpts), ok = rebar_file_utils:delete_each(GeneratedFiles), ok. %% =================================================================== %% Internal functions %% =================================================================== %% @doc Find all files generated from diameter dictionaries. %% %% Scans for .dia files and determines what .erl and .hrl files they would %% generate based on parsing their specifications. This ensures we only %% remove files that are actually generated content. %% %% @param AppDir Application root directory %% @param DiaDir Directory containing .dia files (relative to AppDir) %% @param SrcDir Directory containing generated .erl files (relative to AppDir) %% @param IncDir Directory containing generated .hrl files (relative to AppDir) %% @param DiaOpts Diameter compilation options %% @returns List of file paths to remove %% @private dia_generated_files(AppDir, DiaDir, SrcDir, IncDir, DiaOpts) -> F = fun(File, Acc) -> case catch diameter_dict_util:parse({path, File}, DiaOpts) of {ok, Spec} -> FileName = dia_filename(File, Spec), [ filename:join([AppDir, IncDir, FileName ++ ".hrl"]) | filelib:wildcard( filename:join([ AppDir, SrcDir, FileName ++ ".*" ]) ) ] ++ Acc; _ -> Acc end end, lists:foldl( F, [], filelib:wildcard( filename:join([AppDir, DiaDir, "*.dia"]) ) ). %% @doc Extract the dictionary name from file or spec. %% %% Same logic as in compile_diameter.erl - determines the name to use for %% generated files based on the diameter specification or source filename. %% %% @param File Path to the source .dia file %% @param Spec Parsed diameter dictionary specification %% @returns String name for the dictionary %% @private dia_filename(File, Spec) -> case proplists:get_value(name, Spec) of undefined -> filename:rootname( filename:basename(File) ); Name -> Name end.