Skip to content

Commit

Permalink
Refine interface and run formatter (tests still not updated)
Browse files Browse the repository at this point in the history
  • Loading branch information
GabrielKS committed Sep 25, 2024
1 parent 1015351 commit d0b1b40
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 73 deletions.
143 changes: 78 additions & 65 deletions src/component_selector.jl
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# ABSTRACT TYPE DEFINITIONS
"""The basic type for all ComponentSelectors.
"""The basic type for all `ComponentSelector`s.
Concrete subtypes MUST implement:
- `get_components`: returns an iterable of components
- `get_name`, or use the default by implementing `default_name` and having a `name` field
- `get_groups`: returns an iterable of `ComponentSelector`s
- `get_name`: returns a name for the selector -- or use the default by implementing `default_name` and having a `name` field
- `get_groups`: returns an iterable of `ComponentSelector`s -- or use the default by having a `groupby` field
Concrete subtypes MAY implement:
- The factory method `make_selector` (make sure your signature does not conflict with
Expand All @@ -14,31 +14,30 @@ Concrete subtypes MAY implement:
abstract type ComponentSelector end

"""
ComponentSelectors that are not composed of other ComponentSelectors.
`ComponentSelector`s that can only refer to zero or one components.
The interface is the same as for `ComponentSelector` except
- `get_components` MUST return zero or one components
- the additional method `get_component` is part of the interface, but the default
implementation just wraps `get_components` and should not need to be overridden.
- there is a sensible default for `get_groups`
"""
abstract type ComponentSelectorElement <: ComponentSelector end
abstract type SingularComponentSelector <: ComponentSelector end

"""
ComponentSelectors that are composed of other ComponentSelectors.
`ComponentSelector`s that may refer to multiple components.
The interface is that of `ComponentSelector`.
"""
abstract type ComponentSelectorSet <: ComponentSelector end
abstract type PluralComponentSelector <: ComponentSelector end

"""
ComponentSelectorSets whose grouping is determined by a `groupby` field (all of the built-in
ComponentSelectorSets except ListComponentSelector work this way)
`PluralComponentSelector`s whose grouping is determined by a `groupby` field (all of the
built-in `PluralComponentSelector`s except `ListComponentSelector` work this way)
"""
abstract type DynamicallyGroupedComponentSelectorSet <: ComponentSelectorSet end
abstract type DynamicallyGroupedPluralComponentSelector <: PluralComponentSelector end

# COMMON COMPONENTSELECTOR INFRASTRUCTURE
# TODO perhaps put this elsewhere; it is also referenced in metrics.jl

"Canonical way to turn an InfrastructureSystemsComponent subtype into a unique string."
subtype_to_string(subtype::Type{<:InfrastructureSystemsComponent}) =
strip_module_name(subtype)
Expand All @@ -56,72 +55,99 @@ component_to_qualified_string(component::InfrastructureSystemsComponent) =

# Generic implementations/generic docstrings for simple functions with many methods
"""
Get the default name for the ComponentSelector, constructed automatically from what the
ComponentSelector contains. Particularly with complex ComponentSelectors, this may not
always be very concise or informative, so in these cases constructing the ComponentSelector
with a custom name is recommended.
Get the default name for the `ComponentSelector`, constructed automatically from what the
`ComponentSelector` contains. Particularly with complex `ComponentSelector`s, this may not
always be very concise or informative, so in these cases constructing the
`ComponentSelector` with a custom name is recommended.
"""
function default_name end

# Override this if you define a ComponentSelector subtype with no name field
"""
Get the name of the ComponentSelector. This is either the default name or a custom name passed in at
creation time.
Get the name of the `ComponentSelector`. This is either the default name or a custom name
passed in at creation time.
"""
# Override this if you define a ComponentSelector subtype with no name field
get_name(e::ComponentSelector) = (e.name !== nothing) ? e.name : default_name(e)

# Make all get_components, get_subselectors below that take a Components also work with a SystemData
# Make all get_components below that take a Components also work with a SystemData
"""
Get the components of the collection that make up the ComponentSelector.
Get the components of the collection that make up the `ComponentSelector`.
"""
get_components(e::ComponentSelector, sys::SystemData; filterby = nothing) =
get_components(e, sys.components; filterby = filterby)


"""
Get the component of the collection that makes up the `ComponentSelectorElement`, `nothing`
Get the component of the collection that makes up the `SingularComponentSelector`; `nothing`
if there is none.
"""
get_component(e::ComponentSelectorElement, sys::SystemData; filterby = nothing) =
get_component(e::SingularComponentSelector, sys::SystemData; filterby = nothing) =
get_component(e, sys.components; filterby = filterby)

"""
Get the sub-selectors that make up the ComponentSelectorSet.
Get the groups that make up the `ComponentSelector`.
"""
get_groups(e::ComponentSelector, sys::SystemData; filterby = nothing) =
get_groups(e, sys.components; filterby = filterby)

function get_groups(e::DynamicallyGroupedComponentSelectorSet, sys::Components; filterby = nothing)
"""
Use the `groupby` property to get the groups that make up the
`DynamicallyGroupedPluralComponentSelector`
"""
function get_groups(
e::DynamicallyGroupedPluralComponentSelector,
sys::Components;
filterby = nothing,
)
(e.groupby == :all) && return [e]
(e.groupby == :each) && return Iterators.map(make_selector, get_components(e, sys; filterby = filterby))
(e.groupby == :each) &&
return Iterators.map(make_selector, get_components(e, sys; filterby = filterby))
@assert e.groupby isa Function
components = collect(get_components(e, sys; filterby = filterby))
partition_result = e.groupby.(components)
return [make_selector(make_selector.(components[partition_result .== groupname])..., name = groupname) for groupname in unique(partition_result)]
return [
make_selector(
make_selector.(components[partition_result .== groupname])...;
name = groupname,
) for groupname in unique(partition_result)
]
end

"Get the single group that corresponds to the `SingularComponentSelector`, i.e., itself"
get_groups(e::SingularComponentSelector, sys::Components; filterby = nothing) = [e]

"""
Get the component of the collection that makes up the `SingularComponentSelector`; `nothing`
if there is none.
"""
function get_component(e::SingularComponentSelector, sys::Components; filterby = nothing)
components = get_components(e, sys; filterby = filterby)
isempty(components) && return nothing
return only(components)
end

# CONCRETE SUBTYPE IMPLEMENTATIONS
# SingleComponentSelector
"ComponentSelector that wraps a single Component."
struct SingleComponentSelector <: ComponentSelectorElement
# NameComponentSelector
"`ComponentSelector` that refers by name to at most a single component."
struct NameComponentSelector <: SingularComponentSelector
component_subtype::Type{<:InfrastructureSystemsComponent}
component_name::AbstractString
name::Union{String, Nothing}
end

# Construction
"""
Make a ComponentSelector pointing to an InfrastructureSystemsComponent with the given
subtype and name. Optionally provide a name for the ComponentSelector.
Make a `ComponentSelector` pointing to a component with the given subtype and name.
Optionally provide a name for the `ComponentSelector`.
"""
make_selector(
component_subtype::Type{<:InfrastructureSystemsComponent},
component_name::AbstractString;
name::Union{String, Nothing} = nothing,
) = SingleComponentSelector(component_subtype, component_name, name)
) = NameComponentSelector(component_subtype, component_name, name)
"""
Construct a ComponentSelector from an InfrastructureSystemsComponent reference, pointing to Components in any
collection with the given Component's subtype and name.
Make a `ComponentSelector` from a component reference, pointing to components in any
collection with the given component's subtype and name. Optionally provide a name for the
`ComponentSelector`.
"""
make_selector(
component_ref::InfrastructureSystemsComponent;
Expand All @@ -130,38 +156,29 @@ make_selector(
make_selector(typeof(component_ref), get_name(component_ref); name = name)

# Naming
default_name(e::SingleComponentSelector) =
default_name(e::NameComponentSelector) =
component_to_qualified_string(e.component_subtype, e.component_name)

# Contents
function get_components(e::SingleComponentSelector, sys::Components; filterby = nothing)
function get_components(e::NameComponentSelector, sys::Components; filterby = nothing)
com = get_component(e.component_subtype, sys, e.component_name)
(!isnothing(filterby) && !filterby(com)) && (com = nothing)
return (com === nothing) ? [] : [com]
end

function get_component(e::ComponentSelectorElement, sys::Components; filterby = nothing)
components = get_components(e, sys; filterby = filterby)
isempty(components) && return nothing
return only(components)
end

get_groups(e::SingleComponentSelector, sys::Components; filterby = nothing) = e

# ListComponentSelector
"ComponentSelectorSet represented by a list of other ComponentSelectors."
struct ListComponentSelector <: ComponentSelectorSet
"`PluralComponentSelector` represented by a list of other `ComponentSelector`s."
struct ListComponentSelector <: PluralComponentSelector
# Using tuples internally for immutability => `==` is automatically well-behaved
content::Tuple{Vararg{ComponentSelector}}
name::Union{String, Nothing}
end

# Construction
"""
Make a ComponentSelector pointing to a list of subselectors. Optionally provide a name for
the ComponentSelector.
Make a `ComponentSelector` pointing to a list of sub-selectors, which form the groups.
Optionally provide a name for the `ComponentSelector`.
"""
# name needs to be a kwarg to disambiguate from content
make_selector(content::ComponentSelector...; name::Union{String, Nothing} = nothing) =
ListComponentSelector(content, name)

Expand All @@ -180,23 +197,22 @@ function get_components(e::ListComponentSelector, sys::Components; filterby = no
end

# SubtypeComponentSelector
"ComponentSelectorSet represented by a subtype of Component."
struct SubtypeComponentSelector <: DynamicallyGroupedComponentSelectorSet
"`PluralComponentSelector` represented by a type of component."
struct SubtypeComponentSelector <: DynamicallyGroupedPluralComponentSelector
component_subtype::Type{<:InfrastructureSystemsComponent}
name::Union{String, Nothing}
groupby::Union{Symbol, Function} # TODO add validation
end

# Construction
"""
Make a ComponentSelector from a subtype of Component. Optionally provide a name for the
ComponentSelectorSet.
Make a `ComponentSelector` from a type of component. Optionally provide a name for the
`ComponentSelector`.
"""
# name needs to be a kwarg to disambiguate from SingleComponentSelector's make_selector
make_selector(
component_subtype::Type{<:InfrastructureSystemsComponent};
name::Union{String, Nothing} = nothing,
groupby::Union{Symbol, Function} = :all
groupby::Union{Symbol, Function} = :all,
) =
SubtypeComponentSelector(component_subtype, name, groupby)

Expand All @@ -211,8 +227,8 @@ function get_components(e::SubtypeComponentSelector, sys::Components; filterby =
end

# FilterComponentSelector
"ComponentSelectorSet represented by a filter function and a subtype of Component."
struct FilterComponentSelector <: DynamicallyGroupedComponentSelectorSet
"`PluralComponentSelector` represented by a filter function and a type of component."
struct FilterComponentSelector <: DynamicallyGroupedPluralComponentSelector
component_subtype::Type{<:InfrastructureSystemsComponent}
filter_fn::Function
name::Union{String, Nothing}
Expand All @@ -221,13 +237,14 @@ end

# Construction
"""
Make a ComponentSelector from a filter function and a subtype of Component. Optionally
Make a ComponentSelector from a filter function and a type of component. Optionally
provide a name for the ComponentSelector. The filter function must accept instances of
component_subtype as a sole argument and return a Bool.
"""
function make_selector(
component_subtype::Type{<:InfrastructureSystemsComponent},
filter_fn::Function; name::Union{String, Nothing} = nothing, groupby::Union{Symbol, Function} = :all
filter_fn::Function; name::Union{String, Nothing} = nothing,
groupby::Union{Symbol, Function} = :all,
)
# Try to catch inappropriate filter functions
hasmethod(filter_fn, Tuple{component_subtype}) || throw(
Expand All @@ -244,10 +261,6 @@ function make_selector(
end

# Contents
function get_subselectors(e::FilterComponentSelector, sys::Components; filterby = nothing)
return Iterators.map(make_selector, get_components(e, sys; filterby = filterby))
end

function get_components(e::FilterComponentSelector, sys::Components; filterby = nothing)
components = get_components(e.filter_fn, e.component_subtype, sys)
isnothing(filterby) && (return components)
Expand Down
16 changes: 8 additions & 8 deletions test/test_component_selector.jl
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,17 @@ sort_name!(x) = sort!(collect(x); by = IS.get_name)
"TestComponent__Component1"
end

@testset "Test SingleComponentSelector" begin
@testset "Test NameComponentSelector" begin
# Everything should work for both Components and SystemData
@testset for test_sys in [cstest_make_components(), cstest_make_system_data()]
test_gen_ent = IS.SingleComponentSelector(IS.TestComponent, "Component1", nothing)
test_gen_ent = IS.NameComponentSelector(IS.TestComponent, "Component1", nothing)
named_test_gen_ent =
IS.SingleComponentSelector(IS.TestComponent, "Component1", "CompOne")
IS.NameComponentSelector(IS.TestComponent, "Component1", "CompOne")

# Equality
@test IS.SingleComponentSelector(IS.TestComponent, "Component1", nothing) ==
@test IS.NameComponentSelector(IS.TestComponent, "Component1", nothing) ==
test_gen_ent
@test IS.SingleComponentSelector(IS.TestComponent, "Component1", "CompOne") ==
@test IS.NameComponentSelector(IS.TestComponent, "Component1", "CompOne") ==
named_test_gen_ent

# Construction
Expand Down Expand Up @@ -174,7 +174,7 @@ end

@test collect(
IS.get_subselectors(IS.make_selector(IS.SimpleTestComponent), test_sys),
) == Vector{IS.ComponentSelectorElement}()
) == Vector{IS.SingularComponentSelector}()
the_subselectors = IS.get_subselectors(test_sub_ent, test_sys)
@test all(
sort_name!(the_subselectors) .== sort_name!(IS.make_selector.(answer)),
Expand Down Expand Up @@ -255,12 +255,12 @@ end
IS.get_subselectors(
IS.make_selector(x -> true, IS.SimpleTestComponent),
test_sys,
)) == Vector{IS.ComponentSelectorElement}()
)) == Vector{IS.SingularComponentSelector}()
@test collect(
IS.get_subselectors(
IS.make_selector(x -> false, IS.InfrastructureSystemsComponent),
test_sys,
)) == Vector{IS.ComponentSelectorElement}()
)) == Vector{IS.SingularComponentSelector}()
the_subselectors = IS.get_subselectors(test_filter_ent, test_sys)
@test all(
sort_name!(the_subselectors) .==
Expand Down

0 comments on commit d0b1b40

Please sign in to comment.