Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions ArchUnitNET/Domain/PlantUml/Export/GenerationOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,14 @@ public class GenerationOptions
public bool IncludeNodesWithoutDependencies { get; set; } = true;
public bool LimitDependencies { get; set; } = false;
public bool C4Style { get; set; } = false;

/// <summary>
/// Whether slice diagrams nest each slice in <c>package</c> blocks along the namespace its
/// types matched, instead of drawing every slice on its own under its name. A slice whose
/// types matched different namespaces before the first capture group, or that does not
/// come from a slice pattern, is drawn on its own either way. Slices of the obsolete
/// <c>MatchingWithPackages</c> are always nested.
/// </summary>
public bool NestSlicesInPackages { get; set; } = false;
}
}
195 changes: 148 additions & 47 deletions ArchUnitNET/Domain/PlantUml/Export/PlantUmlFileBuilder.cs

Large diffs are not rendered by default.

24 changes: 17 additions & 7 deletions ArchUnitNET/Domain/Slice.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,25 @@ public Slice(SliceIdentifier identifier, IEnumerable<IType> types)

public string Description => Identifier.Description;

[CanBeNull]
public string NameSpace => Identifier.NameSpace;
public int? CountOfAsteriskInPattern => Identifier.CountOfAsteriskInPattern;

/// <summary>
/// The namespace a diagram nests this slice under when it nests slices in packages: the
/// part of the namespace that the pattern matched before the first capture group, if
/// it is the same for every type in the slice, which may be empty. <c>null</c> if it
/// differs between the types, or if the slice does not come from a slice pattern; a
/// diagram then draws the slice on its own. It takes no part in the slice's identity.
/// </summary>
[CanBeNull]
internal string DiagramNamespace { get; set; }

/// <summary>
/// Whether diagrams nest this slice in packages even if
/// <see cref="PlantUml.Export.GenerationOptions.NestSlicesInPackages"/> is not set, which
/// is the case for slices of the obsolete <c>MatchingWithPackages</c>.
/// </summary>
internal bool NestByDefault { get; set; }

protected bool Equals(Slice other)
{
return Equals(Identifier, other.Identifier);
Expand Down Expand Up @@ -65,10 +80,5 @@ public override string ToString()
{
return Description;
}

public bool ContainsNamespace()
{
return NameSpace != null;
}
}
}
48 changes: 41 additions & 7 deletions ArchUnitNET/Domain/SliceIdentifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,62 @@ private SliceIdentifier(
string identifier,
bool ignored,
int? countOfAsteriskInPattern = null,
string nameSpace = null
string matchedPrefix = null,
string diagramPath = null
)
: base(identifier)
{
Ignored = ignored;
CountOfAsteriskInPattern = countOfAsteriskInPattern;
NameSpace = nameSpace;
MatchedPrefix = matchedPrefix;
DiagramPath = diagramPath;
}

public string Description => Identifier;

/// <summary>
/// The part of the type's namespace the pattern matched before the first capture group,
/// e.g. "App." for "App.(*)" over "App.Orders", or an empty string if the pattern starts
/// with a group; <c>null</c> if the identifier does not come from a slice pattern. Only
/// used to nest the slice in diagrams; it takes no part in the slice's identity.
/// </summary>
[CanBeNull]
public readonly string NameSpace;
internal readonly string MatchedPrefix;

/// <summary>
/// The path a diagram nests the slice along below <see cref="MatchedPrefix"/>: the part of
/// the namespace the pattern captured, e.g. "Orders.Web" for "App.(**)" over
/// "App.Orders.Web". <c>null</c> if the identifier does not come from a slice pattern. It
/// takes no part in the slice's identity.
/// </summary>
[CanBeNull]
internal readonly string DiagramPath;

public readonly int? CountOfAsteriskInPattern;

public static SliceIdentifier Of(
public static SliceIdentifier Of(string identifier, int? countOfAsteriskInPattern = null)
{
return new SliceIdentifier(identifier, false, countOfAsteriskInPattern);
}

/// <summary>
/// Creates the identifier a slice pattern assigns to a type, together with where the
/// pattern matched, which diagrams use to nest the slice.
/// </summary>
internal static SliceIdentifier OfMatch(
string identifier,
int? countOfAsteriskInPattern = null,
string nameSpace = null
int? countOfAsteriskInPattern,
string matchedPrefix,
string diagramPath
)
{
return new SliceIdentifier(identifier, false, countOfAsteriskInPattern, nameSpace);
return new SliceIdentifier(
identifier,
false,
countOfAsteriskInPattern,
matchedPrefix,
diagramPath
);
}

public static SliceIdentifier Ignore()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,26 @@ public GivenPlantUmlFluentComponentDiagram WithDependenciesFromSlices(
return new GivenPlantUmlFluentComponentDiagram(_fluentComponentDiagramCreator);
}

/// <summary>
/// Draws only the dependencies from and to the slices whose diagram name contains
/// <paramref name="focusOnPackage"/>; see
/// <see cref="PlantUmlFileBuilder.WithDependenciesFromFocusOn"/>.
/// </summary>
/// <param name="slices">The slices to draw.</param>
/// <param name="focusOnPackage">The text to focus on, fully qualified when slices are
/// nested in packages.</param>
/// <param name="generationOptions">Only
/// <see cref="GenerationOptions.NestSlicesInPackages"/> is read.</param>
public GivenPlantUmlFluentComponentDiagram WithDependenciesFromSlices(
IEnumerable<Slice> slices,
string focusOnPackage
string focusOnPackage,
GenerationOptions generationOptions = null
)
{
_fluentComponentDiagramCreator.Builder.WithDependenciesFromFocusOn(
slices,
focusOnPackage
focusOnPackage,
generationOptions
);
_fluentComponentDiagramCreator.AddToDescription("with dependencies from slices");
return new GivenPlantUmlFluentComponentDiagram(_fluentComponentDiagramCreator);
Expand Down
46 changes: 41 additions & 5 deletions ArchUnitNET/Fluent/Slices/SliceAssignment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,57 @@ public class SliceAssignment : IHasDescription
public SliceAssignment(
Func<IType, SliceIdentifier> assignIdentifierFunc,
string description
)
: this(assignIdentifierFunc, description, false) { }

internal SliceAssignment(
Func<IType, SliceIdentifier> assignIdentifierFunc,
string description,
bool nestByDefault
)
{
_assignIdentifierFunc = assignIdentifierFunc;
Description = description;
_nestByDefault = nestByDefault;
}

/// <summary>
/// Whether diagrams nest the slices in packages even if
/// <see cref="Domain.PlantUml.Export.GenerationOptions.NestSlicesInPackages"/> is not set;
/// only for the obsolete <c>MatchingWithPackages</c>.
/// </summary>
private readonly bool _nestByDefault;

public string Description { get; }

public IEnumerable<Slice> Apply(IEnumerable<IType> types)
{
return types.GroupBy(
_assignIdentifierFunc,
(identifier, enumerable) => new Slice(identifier, enumerable),
SliceIdentifier.Comparer
);
return types
.Select(type => (Type: type, Identifier: _assignIdentifierFunc(type)))
.GroupBy(
assigned => assigned.Identifier,
(identifier, assigned) => CreateSlice(identifier, assigned.ToList()),
SliceIdentifier.Comparer
);
}

private Slice CreateSlice(
SliceIdentifier identifier,
List<(IType Type, SliceIdentifier Identifier)> assigned
)
{
// The types of one slice can have matched different prefixes, e.g. with a leading
// ".."; such a slice has no single namespace to be nested under.
var prefixes = assigned
.Select(typeAndIdentifier => typeAndIdentifier.Identifier.MatchedPrefix)
.Distinct()
.ToList();
var types = assigned.Select(typeAndIdentifier => typeAndIdentifier.Type).ToList();
return new Slice(identifier, types)
{
DiagramNamespace = prefixes.Count == 1 ? prefixes[0] : null,
NestByDefault = _nestByDefault,
};
}
}
}
63 changes: 34 additions & 29 deletions ArchUnitNET/Fluent/Slices/SliceRuleInitializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,29 +21,36 @@ public SliceRuleInitializer(SliceRuleCreator ruleCreator)
/// <returns></returns>
public GivenSlices Matching(string pattern)
{
_ruleCreator.SetSliceAssignment(
new SliceAssignment(
t =>
{
var parseResult = Parse(pattern);
return AssignFunc(t, parseResult.pattern, parseResult.asterisk);
},
"matching \"" + pattern + "\""
)
);
return new GivenSlices(_ruleCreator);
return Matching(pattern, false);
}

/// <summary>
/// Creates the same slices as <see cref="Matching"/>, which PlantUML diagrams nest in
/// <c>package</c> blocks even without
/// <see cref="Domain.PlantUml.Export.GenerationOptions.NestSlicesInPackages"/>.
/// </summary>
/// <param name="pattern">The namespace pattern, as for <see cref="Matching"/>.</param>
[Obsolete(
"Use Matching(pattern) and draw the slices with "
+ "new GenerationOptions { NestSlicesInPackages = true }. MatchingWithPackages "
+ "creates the same slices as Matching and will be removed in a future release."
)]
public GivenSlices MatchingWithPackages(string pattern)
{
return Matching(pattern, true);
}

private GivenSlices Matching(string pattern, bool nestByDefault)
{
_ruleCreator.SetSliceAssignment(
new SliceAssignment(
t =>
{
var parseResult = Parse(pattern);
return AssignFunc(t, parseResult.pattern, parseResult.asterisk, true);
return AssignFunc(t, parseResult.pattern, parseResult.asterisk);
},
"matching \"" + pattern + "\""
"matching \"" + pattern + "\"",
nestByDefault
)
);
return new GivenSlices(_ruleCreator);
Expand Down Expand Up @@ -87,8 +94,7 @@ private static (string pattern, int? asterisk) Parse(string pattern)
private static SliceIdentifier AssignFunc(
IType type,
string pattern,
int? countOfSingleAsterisk,
bool fullName = false
int? countOfSingleAsterisk
)
{
var indexOfAsteriskInPattern = pattern.IndexOf("(*", StringComparison.Ordinal);
Expand Down Expand Up @@ -133,14 +139,14 @@ private static SliceIdentifier AssignFunc(
}

var sliceString = namespc;
var matchedPrefix = "";

if (slicePrefix != "")
{
sliceString = namespc
.Substring(
namespc.IndexOf(slicePrefix, StringComparison.Ordinal) + slicePrefix.Length
)
.TrimStart('.');
var prefixEnd =
namespc.IndexOf(slicePrefix, StringComparison.Ordinal) + slicePrefix.Length;
matchedPrefix = namespc.Substring(0, prefixEnd);
sliceString = namespc.Substring(prefixEnd).TrimStart('.');
}

if (!sliceString.Contains(slicePostfix))
Expand All @@ -161,15 +167,14 @@ private static SliceIdentifier AssignFunc(
);
}

// An empty prefix means the pattern starts with its capture group; there is no
// namespace to nest the slice under, so report none rather than an empty one.
return fullName
? SliceIdentifier.Of(
slicePrefix + sliceString,
countOfSingleAsterisk,
slicePrefix == "" ? null : slicePrefix
)
: SliceIdentifier.Of(sliceString, countOfSingleAsterisk);
// What the pattern captured is both what identifies the slice and the path a
// diagram nests it along.
return SliceIdentifier.OfMatch(
sliceString,
countOfSingleAsterisk,
matchedPrefix,
sliceString
);
}
}
}
Loading
Loading