From c03b05c2238eac368ca3108ccda584d8bdd1e219 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Wed, 11 Mar 2026 09:29:54 +0000 Subject: [PATCH 1/2] Add ExpressionSource operator An expression script used to generate a sequence with a single element. The default value `it` is assigned to the singleton `Unit` object. --- .../ExpressionScriptEditor.cs | 3 +- .../ExpressionSource.cs | 78 +++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 src/Bonsai.Scripting.Expressions/ExpressionSource.cs diff --git a/src/Bonsai.Scripting.Expressions.Design/ExpressionScriptEditor.cs b/src/Bonsai.Scripting.Expressions.Design/ExpressionScriptEditor.cs index 7d151f3..a87a1d0 100644 --- a/src/Bonsai.Scripting.Expressions.Design/ExpressionScriptEditor.cs +++ b/src/Bonsai.Scripting.Expressions.Design/ExpressionScriptEditor.cs @@ -4,6 +4,7 @@ using System.Windows.Forms.Design; using System.Windows.Forms; using Bonsai.Design; +using System.Reactive; namespace Bonsai.Scripting.Expressions.Design { @@ -53,7 +54,7 @@ public override object EditValue(ITypeDescriptorContext context, IServiceProvide var editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService)); if (editorService != null) { - var itType = GetDataSource(context, provider)?.ObservableType; + var itType = context.Instance is ExpressionSource ? typeof(Unit) : GetDataSource(context, provider)?.ObservableType; using var editorDialog = new ExpressionScriptEditorDialog(itType); editorDialog.Script = (string)value; return editorService.ShowDialog(editorDialog) == DialogResult.OK diff --git a/src/Bonsai.Scripting.Expressions/ExpressionSource.cs b/src/Bonsai.Scripting.Expressions/ExpressionSource.cs new file mode 100644 index 0000000..e25bb71 --- /dev/null +++ b/src/Bonsai.Scripting.Expressions/ExpressionSource.cs @@ -0,0 +1,78 @@ +using Bonsai.Expressions; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq.Expressions; +using System.Reactive; +using System.Reactive.Linq; + +namespace Bonsai.Scripting.Expressions +{ + /// + /// Represents an operator that uses an expression script to generate an + /// observable sequence with a single element. + /// + [DefaultProperty(nameof(Expression))] + [WorkflowElementCategory(ElementCategory.Source)] + [TypeDescriptionProvider(typeof(ExpressionSourceTypeDescriptionProvider))] + [Description("An expression script used to generate a sequence with a single element.")] + public class ExpressionSource : ZeroArgumentExpressionBuilder, IScriptingElement + { + /// + /// Gets or sets the name of the expression source. + /// + [Externalizable(false)] + [Category(nameof(CategoryAttribute.Design))] + [Description("The name of the expression source.")] + public string Name { get; set; } + + /// + /// Gets or sets a description for the expression source. + /// + [Externalizable(false)] + [Category(nameof(CategoryAttribute.Design))] + [Description("A description for the expression source.")] + [Editor(DesignTypes.MultilineStringEditor, DesignTypes.UITypeEditor)] + public string Description { get; set; } + + /// + /// Gets or sets the expression that generates the singleton element. + /// + /// + /// The it parameter stores the singleton object + /// representing no data. + /// + [Editor("Bonsai.Scripting.Expressions.Design.ExpressionScriptEditor, Bonsai.Scripting.Expressions.Design", DesignTypes.UITypeEditor)] + [Description("The expression that generates the singleton element.")] + public string Expression { get; set; } = "it"; + + /// + public override Expression Build(IEnumerable arguments) + { + var config = ParsingConfigHelper.CreateParsingConfig(typeof(Unit)); + var generator = DynamicExpressionHelper.ParseLambda(config, typeof(Unit), null, Expression); + return System.Linq.Expressions.Expression.Call(typeof(ExpressionSource), nameof(Process), new[] { generator.ReturnType }, generator); + } + + static IObservable Process(Func generator) + { + return Observable.Return(Unit.Default).Select(generator); + } + + class ExpressionSourceTypeDescriptionProvider : TypeDescriptionProvider + { + static readonly TypeDescriptionProvider parentProvider = TypeDescriptor.GetProvider(typeof(ExpressionSource)); + + public ExpressionSourceTypeDescriptionProvider() + : base(parentProvider) + { + } + + public override ICustomTypeDescriptor GetExtendedTypeDescriptor(object instance) + { + return new ScriptingElementTypeDescriptor(instance, + "An expression script used to generate a sequence with a single element."); + } + } + } +} From d034294280ed5fd0588c71d753bae01374446db6 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Sun, 15 Mar 2026 17:55:03 +0000 Subject: [PATCH 2/2] Ensure inline code element in XML docs --- src/Bonsai.Scripting.Expressions/ExpressionSource.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bonsai.Scripting.Expressions/ExpressionSource.cs b/src/Bonsai.Scripting.Expressions/ExpressionSource.cs index e25bb71..7d1a96c 100644 --- a/src/Bonsai.Scripting.Expressions/ExpressionSource.cs +++ b/src/Bonsai.Scripting.Expressions/ExpressionSource.cs @@ -39,7 +39,7 @@ public class ExpressionSource : ZeroArgumentExpressionBuilder, IScriptingElement /// Gets or sets the expression that generates the singleton element. /// /// - /// The it parameter stores the singleton object + /// The it parameter stores the singleton object /// representing no data. /// [Editor("Bonsai.Scripting.Expressions.Design.ExpressionScriptEditor, Bonsai.Scripting.Expressions.Design", DesignTypes.UITypeEditor)]