@@ -96,14 +96,16 @@ defmodule Drops.Operations do
9696
9797 @ spec define ( keyword ( ) ) :: Macro . t ( )
9898 def define ( opts ) do
99+ { ordered_extensions , set_opts } =
100+ resolve_extension_dependencies ( opts [ :extensions ] , opts )
101+
99102 use_extensions =
100- Enum . map ( opts [ :extensions ] , & quote ( do: use ( unquote ( & 1 ) , unquote ( opts ) ) ) )
101- |> Enum . reverse ( )
103+ Enum . map ( ordered_extensions , & quote ( do: use ( unquote ( & 1 ) , unquote ( set_opts ) ) ) )
102104
103105 quote location: :keep do
104106 import Drops.Operations
105107
106- @ opts unquote ( opts )
108+ @ opts unquote ( set_opts )
107109
108110 @ unit_of_work Drops.Operations.UnitOfWork . new ( __MODULE__ , [ ] )
109111
@@ -149,7 +151,7 @@ defmodule Drops.Operations do
149151 module = env . module
150152
151153 opts = Module . get_attribute ( module , :opts )
152- enabled_extensions = Module . get_attribute ( module , :enabled_extensions )
154+ enabled_extensions = Enum . reverse ( Module . get_attribute ( module , :enabled_extensions ) )
153155 custom_steps = Module . get_attribute ( module , :steps , [ ] )
154156
155157 extension_steps = Enum . map ( enabled_extensions , fn extension -> extension . steps ( ) end )
@@ -352,4 +354,109 @@ defmodule Drops.Operations do
352354
353355 Keyword . merge ( parent_opts , new_opts ) |> Keyword . put ( :extensions , extensions )
354356 end
357+
358+ @ spec resolve_extension_dependencies ( [ module ( ) ] , keyword ( ) ) :: { [ module ( ) ] , keyword ( ) }
359+ defp resolve_extension_dependencies ( extensions , opts ) do
360+ # Build dependency graph
361+ all_extensions = collect_all_extensions ( extensions , [ ] )
362+ ordered_extensions = topological_sort ( all_extensions )
363+
364+ # Collect and merge default options from all extensions
365+ extension_opts =
366+ Enum . reduce ( ordered_extensions , [ ] , fn extension , acc ->
367+ if function_exported? ( extension , :default_opts , 1 ) do
368+ extension_defaults = extension . default_opts ( opts )
369+ merge_opts ( acc , extension_defaults )
370+ else
371+ acc
372+ end
373+ end )
374+
375+ # Merge extension options with user-provided options
376+ merged_opts = merge_opts ( extension_opts , opts )
377+
378+ { ordered_extensions , merged_opts }
379+ end
380+
381+ @ spec get_extension_dependencies ( module ( ) ) :: [ module ( ) ]
382+ defp get_extension_dependencies ( extension ) when is_atom ( extension ) do
383+ # Get the @depends_on module attribute from the extension
384+ case extension . __info__ ( :attributes ) [ :depends_on ] do
385+ dependencies when is_list ( dependencies ) -> dependencies
386+ _ -> [ ]
387+ end
388+ end
389+
390+ # Handle AST nodes (during compilation) - they don't have dependencies yet
391+ defp get_extension_dependencies ( _extension ) , do: [ ]
392+
393+ @ spec collect_all_extensions ( [ module ( ) ] , [ module ( ) ] ) :: [ module ( ) ]
394+ defp collect_all_extensions ( [ ] , acc ) , do: Enum . reverse ( acc )
395+
396+ defp collect_all_extensions ( [ extension | rest ] , acc ) do
397+ if extension in acc do
398+ collect_all_extensions ( rest , acc )
399+ else
400+ dependencies = get_extension_dependencies ( extension )
401+ acc_with_deps = collect_all_extensions ( dependencies , [ extension | acc ] )
402+ collect_all_extensions ( rest , acc_with_deps )
403+ end
404+ end
405+
406+ @ spec topological_sort ( [ module ( ) ] ) :: [ module ( ) ]
407+ defp topological_sort ( extensions ) do
408+ # Simple topological sort using Kahn's algorithm
409+ # Build adjacency list and in-degree count
410+ { graph , in_degree } = build_dependency_graph ( extensions )
411+
412+ # Find nodes with no incoming edges
413+ queue = Enum . filter ( extensions , fn ext -> Map . get ( in_degree , ext , 0 ) == 0 end )
414+
415+ sort_extensions ( queue , graph , in_degree , [ ] )
416+ end
417+
418+ @ spec build_dependency_graph ( [ module ( ) ] ) ::
419+ { % { module ( ) => [ module ( ) ] } , % { module ( ) => integer ( ) } }
420+ defp build_dependency_graph ( extensions ) do
421+ graph = Map . new ( extensions , fn ext -> { ext , [ ] } end )
422+ in_degree = Map . new ( extensions , fn ext -> { ext , 0 } end )
423+
424+ Enum . reduce ( extensions , { graph , in_degree } , fn ext , { g , deg } ->
425+ dependencies = get_extension_dependencies ( ext )
426+
427+ Enum . reduce ( dependencies , { g , deg } , fn dep , { graph_acc , degree_acc } ->
428+ # dep -> ext (dependency points to dependent)
429+ graph_acc = Map . update ( graph_acc , dep , [ ext ] , fn deps -> [ ext | deps ] end )
430+ degree_acc = Map . update ( degree_acc , ext , 1 , fn count -> count + 1 end )
431+ { graph_acc , degree_acc }
432+ end )
433+ end )
434+ end
435+
436+ @ spec sort_extensions ( [ module ( ) ] , % { module ( ) => [ module ( ) ] } , % { module ( ) => integer ( ) } , [
437+ module ( )
438+ ] ) :: [ module ( ) ]
439+ defp sort_extensions ( [ ] , _graph , _in_degree , result ) , do: Enum . reverse ( result )
440+
441+ defp sort_extensions ( [ current | queue ] , graph , in_degree , result ) do
442+ # Add current to result
443+ new_result = [ current | result ]
444+
445+ # For each dependent of current, decrease in-degree
446+ dependents = Map . get ( graph , current , [ ] )
447+
448+ { new_queue , new_in_degree } =
449+ Enum . reduce ( dependents , { queue , in_degree } , fn dependent , { q , deg } ->
450+ new_degree = Map . get ( deg , dependent ) - 1
451+ new_deg = Map . put ( deg , dependent , new_degree )
452+
453+ if new_degree == 0 do
454+ { [ dependent | q ] , new_deg }
455+ else
456+ { q , new_deg }
457+ end
458+ end )
459+
460+ sort_extensions ( new_queue , graph , new_in_degree , new_result )
461+ end
355462end
0 commit comments