Skip to content

Commit 461ad82

Browse files
authored
feat(composer): Add Composer pattern source generator (#103)
1 parent 4e5e260 commit 461ad82

7 files changed

Lines changed: 1702 additions & 2 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace PatternKit.Generators.Composer;
2+
3+
/// <summary>
4+
/// Marks a method to be excluded from pipeline composition.
5+
/// Use this to explicitly exclude methods that might otherwise be considered for composition.
6+
/// </summary>
7+
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
8+
public sealed class ComposeIgnoreAttribute : Attribute
9+
{
10+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
namespace PatternKit.Generators.Composer;
2+
3+
/// <summary>
4+
/// Marks a method as a pipeline step that will be composed into the pipeline.
5+
/// Steps are ordered by the Order property and wrap each other according to the ComposerWrapOrder.
6+
/// </summary>
7+
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
8+
public sealed class ComposeStepAttribute : Attribute
9+
{
10+
/// <summary>
11+
/// Gets or sets the order of this step in the pipeline.
12+
/// With OuterFirst (default): Lower Order values wrap higher Order values.
13+
/// - Order=0 executes first, wrapping all other steps.
14+
/// With InnerFirst: Higher Order values wrap lower Order values.
15+
/// - Order=0 executes last, closest to the terminal.
16+
/// </summary>
17+
public int Order { get; set; }
18+
19+
/// <summary>
20+
/// Gets or sets an optional name for this step (for diagnostics and debugging).
21+
/// </summary>
22+
public string? Name { get; set; }
23+
24+
/// <summary>
25+
/// Initializes a new instance of the <see cref="ComposeStepAttribute"/> class.
26+
/// </summary>
27+
/// <param name="order">The order of this step in the pipeline.</param>
28+
public ComposeStepAttribute(int order)
29+
{
30+
Order = order;
31+
}
32+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
namespace PatternKit.Generators.Composer;
2+
3+
/// <summary>
4+
/// Marks a method as the terminal step of the pipeline.
5+
/// The terminal is the final step that produces the output without calling a 'next' delegate.
6+
/// A pipeline must have exactly one terminal.
7+
/// </summary>
8+
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
9+
public sealed class ComposeTerminalAttribute : Attribute
10+
{
11+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
namespace PatternKit.Generators.Composer;
2+
3+
/// <summary>
4+
/// Marks a partial type as a composer pipeline host that will generate deterministic
5+
/// composition of ordered components into a single executable pipeline.
6+
/// </summary>
7+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)]
8+
public sealed class ComposerAttribute : Attribute
9+
{
10+
/// <summary>
11+
/// Gets or sets the name of the generated synchronous invoke method.
12+
/// Default is "Invoke".
13+
/// </summary>
14+
public string InvokeMethodName { get; set; } = "Invoke";
15+
16+
/// <summary>
17+
/// Gets or sets the name of the generated asynchronous invoke method.
18+
/// Default is "InvokeAsync".
19+
/// </summary>
20+
public string InvokeAsyncMethodName { get; set; } = "InvokeAsync";
21+
22+
/// <summary>
23+
/// Gets or sets whether to generate async methods.
24+
/// When null (default), async generation is inferred from the presence of async steps or terminal.
25+
/// Note: Nullable bool in attributes is non-standard but supported by C#.
26+
/// Set to true/false explicitly to control async generation, or leave unset for inference.
27+
/// </summary>
28+
public bool? GenerateAsync { get; set; }
29+
30+
/// <summary>
31+
/// Gets or sets whether to force async generation even if all steps are synchronous.
32+
/// Default is false.
33+
/// </summary>
34+
public bool ForceAsync { get; set; }
35+
36+
/// <summary>
37+
/// Gets or sets the wrapping order for pipeline steps.
38+
/// Default is OuterFirst (step with Order=0 is outermost).
39+
/// </summary>
40+
public ComposerWrapOrder WrapOrder { get; set; } = ComposerWrapOrder.OuterFirst;
41+
}
42+
43+
/// <summary>
44+
/// Defines the order in which pipeline steps wrap each other.
45+
/// </summary>
46+
public enum ComposerWrapOrder
47+
{
48+
/// <summary>
49+
/// Steps with lower Order values wrap steps with higher Order values.
50+
/// Order=0 is the outermost wrapper (executes first).
51+
/// </summary>
52+
OuterFirst = 0,
53+
54+
/// <summary>
55+
/// Steps with higher Order values wrap steps with lower Order values.
56+
/// Order=0 is the innermost wrapper (executes last, closest to terminal).
57+
/// </summary>
58+
InnerFirst = 1
59+
}

0 commit comments

Comments
 (0)