Fix bug where numeric prompts did not get min/max.
This commit is contained in:
Chris McConnell 2016-03-27 14:24:48 -07:00
Родитель 1877bc7203
Коммит 1e1f4b3146
15 изменённых файлов: 1517 добавлений и 1239 удалений

Просмотреть файл

@ -280,9 +280,12 @@ namespace Microsoft.Bot.Builder.Form
/// All possible templates.
/// </summary>
/// <returns>The possible templates.</returns>
public string[] Patterns()
public string[] Patterns
{
return _patterns;
get
{
return _patterns;
}
}
/// <summary>

Просмотреть файл

@ -56,8 +56,8 @@ namespace Microsoft.Bot.Builder.Form.Advanced
: base(Guid.NewGuid().ToString(), FieldRole.Confirm, form)
{
this
.Description(_name)
.Prompt(prompt)
.SetFieldDescription(_name)
.SetPrompt(prompt)
.AddDescription(true, "Yes")
.AddTerms(true, new string[] { "yes", "y", "sure", "ok" })
.AddDescription(false, "No")
@ -74,11 +74,14 @@ namespace Microsoft.Bot.Builder.Form.Advanced
return null;
}
public override IEnumerable<string> Dependencies()
{
return _dependencies;
}
public override IEnumerable<string> Dependencies
{
get
{
return _dependencies;
}
}
#region IFieldPrompt
public override bool Active(T state)
{

Просмотреть файл

@ -96,14 +96,20 @@ namespace Microsoft.Bot.Builder.Form.Advanced
throw new NotImplementedException();
}
public virtual bool Optional()
public virtual bool Optional
{
return _optional;
get
{
return _optional;
}
}
public virtual bool IsNullable()
public virtual bool IsNullable
{
return _isNullable;
get
{
return _isNullable;
}
}
public virtual bool Limits(out double min, out double max)
@ -113,26 +119,36 @@ namespace Microsoft.Bot.Builder.Form.Advanced
return _limited;
}
public virtual IEnumerable<string> Dependencies()
public virtual IEnumerable<string> Dependencies
{
return new string[0];
}
#endregion
#region IFieldDescription
public virtual FieldRole Role()
{
return _role;
get
{
return new string[0];
}
}
public virtual string Description()
public virtual FieldRole Role
{
return _description;
get
{
return _role;
}
}
public virtual IEnumerable<string> Terms()
public virtual string FieldDescription
{
return _terms;
get
{
return _description;
}
}
public virtual IEnumerable<string> FieldTerms
{
get
{
return _terms;
}
}
public virtual IEnumerable<string> Terms(object value)
@ -145,30 +161,45 @@ namespace Microsoft.Bot.Builder.Form.Advanced
return _valueDescriptions[value];
}
public virtual IEnumerable<string> ValueDescriptions()
public virtual IEnumerable<string> ValueDescriptions
{
return (from entry in _valueDescriptions select entry.Value);
get
{
return (from entry in _valueDescriptions select entry.Value);
}
}
public virtual IEnumerable<object> Values()
public virtual IEnumerable<object> Values
{
return (from entry in _valueDescriptions select entry.Key);
get
{
return (from entry in _valueDescriptions select entry.Key);
}
}
public virtual bool AllowsMultiple()
public virtual bool AllowsMultiple
{
return _allowsMultiple;
get
{
return _allowsMultiple;
}
}
public virtual bool AllowDefault()
public virtual bool AllowDefault
{
return _promptDefinition.AllowDefault != BoolDefault.False;
get
{
return _promptDefinition.AllowDefault != BoolDefault.False;
}
}
public bool AllowNumbers()
public bool AllowNumbers
{
_promptDefinition.ApplyDefaults(_form.Configuration.DefaultPrompt);
return _promptDefinition.AllowNumbers;
get
{
_promptDefinition.ApplyDefaults(_form.Configuration.DefaultPrompt);
return _promptDefinition.AllowNumbers;
}
}
#endregion
@ -222,7 +253,7 @@ namespace Microsoft.Bot.Builder.Form.Advanced
/// <summary>Set the field description. </summary>
/// <param name="description">Field description. </param>
/// <returns> A Field&lt;T&gt; </returns>
public Field<T> Description(string description)
public Field<T> SetFieldDescription(string description)
{
UpdateAnnotations();
_description = description;
@ -232,7 +263,7 @@ namespace Microsoft.Bot.Builder.Form.Advanced
/// <summary> Set the help associated with a field. </summary>
/// <param name="help"> Help string. </param>
/// <returns> A Field&lt;T&gt; </returns>
public Field<T> Help(Prompt help)
public Field<T> SetHelp(Prompt help)
{
UpdateAnnotations();
_help = help;
@ -242,7 +273,7 @@ namespace Microsoft.Bot.Builder.Form.Advanced
/// <summary> Set the terms associated with the field. </summary>
/// <param name="terms"> The terms. </param>
/// <returns> A Field&lt;T&gt; </returns>
public Field<T> Terms(IEnumerable<string> terms)
public Field<T> SetFieldTerms(IEnumerable<string> terms)
{
UpdateAnnotations();
_terms = terms.ToArray();
@ -285,7 +316,7 @@ namespace Microsoft.Bot.Builder.Form.Advanced
/// <summary> Set whether or not a field is optional. </summary>
/// <param name="optional"> True if field is optional. </param>
/// <returns> A Field&lt;T&gt; </returns>
public Field<T> Optional(bool optional = true)
public Field<T> SetOptional(bool optional = true)
{
UpdateAnnotations();
_optional = optional;
@ -295,7 +326,7 @@ namespace Microsoft.Bot.Builder.Form.Advanced
/// <summary> Set whether or not field is nullable. </summary>
/// <param name="nullable"> True if field is nullable. </param>
/// <returns> A Field&lt;T&gt; </returns>
public Field<T> IsNullable(bool nullable = true)
public Field<T> SetIsNullable(bool nullable = true)
{
UpdateAnnotations();
_isNullable = nullable;
@ -305,7 +336,7 @@ namespace Microsoft.Bot.Builder.Form.Advanced
/// <summary> Sets the field prompt. </summary>
/// <param name="prompt"> The prompt. </param>
/// <returns> A Field&lt;T&gt; </returns>
public Field<T> Prompt(Prompt prompt)
public Field<T> SetPrompt(Prompt prompt)
{
UpdateAnnotations();
_promptDefinition = prompt;
@ -315,17 +346,17 @@ namespace Microsoft.Bot.Builder.Form.Advanced
/// <summary> Sets the recognizer for the field. </summary>
/// <param name="recognizer"> The recognizer for the field. </param>
/// <returns> A Field&lt;T&gt; </returns>
public Field<T> Recognizer(IRecognize<T> recognizer)
public Field<T> SetRecognizer(IRecognize<T> recognizer)
{
UpdateAnnotations();
_recognizer = recognizer;
return this;
}
/// <summary> Add a template to the field. </summary>
/// <summary> Replace a template in the field. </summary>
/// <param name="template"> The template. </param>
/// <returns> A Field&lt;T&gt; </returns>
public Field<T> Template(Template template)
public Field<T> ReplaceTemplate(Template template)
{
UpdateAnnotations();
AddTemplate(template);
@ -335,7 +366,7 @@ namespace Microsoft.Bot.Builder.Form.Advanced
/// <summary> Set the field validation. </summary>
/// <param name="validate"> The validator. </param>
/// <returns> An IField&lt;T&gt; </returns>
public IField<T> Validate(ValidateDelegate<T> validate)
public IField<T> SetValidation(ValidateDelegate<T> validate)
{
UpdateAnnotations();
_validate = validate;
@ -346,7 +377,7 @@ namespace Microsoft.Bot.Builder.Form.Advanced
/// <param name="min"> The minimum. </param>
/// <param name="max"> The maximum. </param>
/// <returns> An IField&lt;T&gt; </returns>
public IField<T> Numeric(double min, double max)
public IField<T> SetLimits(double min, double max)
{
UpdateAnnotations();
SetLimits(min, max, true);

Просмотреть файл

@ -1,289 +1,366 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license.
//
// Microsoft Bot Framework: http://botframework.com
//
// Bot Builder SDK Github:
// https://github.com/Microsoft/BotBuilder
//
// Copyright (c) Microsoft Corporation
// All rights reserved.
//
// MIT License:
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.Bot.Builder.Form.Advanced;
namespace Microsoft.Bot.Builder.Form
{
public sealed class FormBuilder<T> : IFormBuilder<T>
where T : class
{
private readonly Form<T> _form;
/// <summary>
/// Construct the form builder.
/// </summary>
/// <param name="ignoreAnnotations">True if you want to ignore any annotations on classes when doing reflection.</param>
public FormBuilder(bool ignoreAnnotations = false)
{
_form = new Form<T>(ignoreAnnotations);
}
public IForm<T> Build()
{
if (!_form._steps.Any((step) => step.Type == StepType.Field))
{
var paths = new List<string>();
FormBuilder<T>.FieldPaths(typeof(T), "", paths);
IFormBuilder<T> builder = this;
foreach (var path in paths)
{
builder.Field(new FieldReflector<T>(path, _form));
}
builder.Confirm("Is this your selection?\n{*}");
}
return this._form;
}
public FormConfiguration Configuration { get { return _form._configuration; } }
public IFormBuilder<T> Message(string message, ConditionalDelegate<T> condition = null)
{
_form._steps.Add(new MessageStep<T>(new Prompt(message), condition, _form));
return this;
}
public IFormBuilder<T> Message(Prompt prompt, ConditionalDelegate<T> condition = null)
{
_form._steps.Add(new MessageStep<T>(prompt, condition, _form));
return this;
}
public IFormBuilder<T> Field(string name, ConditionalDelegate<T> condition = null, ValidateDelegate<T> validate = null)
{
var field = (condition == null ? new FieldReflector<T>(name, _form) : new Conditional<T>(name, _form, condition));
if (validate != null)
{
field.Validate(validate);
}
return AddField(field);
}
public IFormBuilder<T> Field(string name, string prompt, ConditionalDelegate<T> condition = null, ValidateDelegate<T> validate = null)
{
var field = (condition == null ? new FieldReflector<T>(name, _form) : new Conditional<T>(name, _form, condition));
if (validate != null)
{
field.Validate(validate);
}
field.Prompt(new Prompt(prompt));
return AddField(field);
}
public IFormBuilder<T> Field(string name, Prompt prompt, ConditionalDelegate<T> condition = null, ValidateDelegate<T> validate = null)
{
var field = (condition == null ? new FieldReflector<T>(name, _form) : new Conditional<T>(name, _form, condition));
if (validate != null)
{
field.Validate(validate);
}
field.Prompt(prompt);
return AddField(field);
}
public IFormBuilder<T> Field(IField<T> field)
{
return AddField(field);
}
public IFormBuilder<T> AddRemainingFields(IEnumerable<string> exclude = null)
{
var exclusions = (exclude == null ? new string[0] : exclude.ToArray());
var paths = new List<string>();
FieldPaths(typeof(T), "", paths);
foreach (var path in paths)
{
if (!exclusions.Contains(path))
{
IField<T> field = _form._fields.Field(path);
if (field == null)
{
AddField(new FieldReflector<T>(path, _form));
}
}
}
return this;
}
public IFormBuilder<T> Confirm(string prompt, ConditionalDelegate<T> condition = null, IEnumerable<string> dependencies = null)
{
IFormBuilder<T> builder = this;
return builder.Confirm(new Prompt(prompt) { ChoiceFormat = "{1}", AllowDefault = BoolDefault.False }, condition, dependencies);
}
public IFormBuilder<T> Confirm(Prompt prompt, ConditionalDelegate<T> condition = null, IEnumerable<string> dependencies = null)
{
if (condition == null) condition = state => true;
if (dependencies == null)
{
// Default next steps go from previous field ignoring confirmations back to next confirmation
// Last field before confirmation
var end = _form._steps.Count();
while (end > 0)
{
if (_form._steps[end - 1].Type == StepType.Field)
{
break;
}
--end;
}
var start = end;
while (start > 0)
{
if (_form._steps[start - 1].Type == StepType.Confirm)
{
break;
}
--start;
}
var fields = new List<string>();
for (var i = start; i < end; ++i)
{
if (_form._steps[i].Type == StepType.Field)
{
fields.Add(_form._steps[i].Name);
}
}
dependencies = fields;
}
var confirmation = new Confirmation<T>(prompt, condition, dependencies, _form);
_form._fields.Add(confirmation);
_form._steps.Add(new ConfirmStep<T>(confirmation));
return this;
}
public IFormBuilder<T> Confirm(IFieldPrompt<T> prompt)
{
// TODO: Need to fill this in
return this;
}
public IFormBuilder<T> OnCompletionAsync(CompletionDelegate<T> callback)
{
_form._completion = callback;
return this;
}
private IFormBuilder<T> AddField(IField<T> field)
{
_form._fields.Add(field);
var step = new FieldStep<T>(field.Name, _form);
var stepIndex = this._form._steps.FindIndex(s => s.Name == field.Name);
if (stepIndex >= 0)
{
_form._steps[stepIndex] = step;
}
else
{
_form._steps.Add(step);
}
return this;
}
internal static void FieldPaths(Type type, string path, List<string> paths)
{
var newPath = (path == "" ? path : path + ".");
foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance))
{
TypePaths(field.FieldType, newPath + field.Name, paths);
}
foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (property.CanRead && property.CanWrite)
{
TypePaths(property.PropertyType, newPath + property.Name, paths);
}
}
}
internal static void TypePaths(Type type, string path, List<string> paths)
{
if (type.IsClass)
{
if (type == typeof(string))
{
paths.Add(path);
}
else if (type.IsIEnumerable())
{
var elt = type.GetGenericElementType();
if (elt.IsEnum)
{
paths.Add(path);
}
else
{
// TODO: What to do about enumerations of things other than enums?
}
}
else
{
FieldPaths(type, path, paths);
}
}
else if (type.IsEnum)
{
paths.Add(path);
}
else if (type == typeof(bool))
{
paths.Add(path);
}
else if (type.IsIntegral())
{
paths.Add(path);
}
else if (type.IsDouble())
{
paths.Add(path);
}
else if (type.IsNullable() && type.IsValueType)
{
paths.Add(path);
}
else if (type == typeof(DateTime))
{
paths.Add(path);
}
}
}
}
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license.
//
// Microsoft Bot Framework: http://botframework.com
//
// Bot Builder SDK Github:
// https://github.com/Microsoft/BotBuilder
//
// Copyright (c) Microsoft Corporation
// All rights reserved.
//
// MIT License:
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.Bot.Builder.Form.Advanced;
namespace Microsoft.Bot.Builder.Form
{
public sealed class FormBuilder<T> : IFormBuilder<T>
where T : class
{
private readonly Form<T> _form;
/// <summary>
/// Construct the form builder.
/// </summary>
/// <param name="ignoreAnnotations">True if you want to ignore any annotations on classes when doing reflection.</param>
public FormBuilder(bool ignoreAnnotations = false)
{
_form = new Form<T>(ignoreAnnotations);
}
public IForm<T> Build()
{
if (!_form._steps.Any((step) => step.Type == StepType.Field))
{
var paths = new List<string>();
FormBuilder<T>.FieldPaths(typeof(T), "", paths);
IFormBuilder<T> builder = this;
foreach (var path in paths)
{
builder.Field(new FieldReflector<T>(path, _form));
}
builder.Confirm("Is this your selection?\n{*}");
}
Validate();
return this._form;
}
public FormConfiguration Configuration { get { return _form._configuration; } }
public IFormBuilder<T> Message(string message, ConditionalDelegate<T> condition = null)
{
_form._steps.Add(new MessageStep<T>(new Prompt(message), condition, _form));
return this;
}
public IFormBuilder<T> Message(Prompt prompt, ConditionalDelegate<T> condition = null)
{
_form._steps.Add(new MessageStep<T>(prompt, condition, _form));
return this;
}
public IFormBuilder<T> Field(string name, ConditionalDelegate<T> condition = null, ValidateDelegate<T> validate = null)
{
var field = (condition == null ? new FieldReflector<T>(name, _form) : new Conditional<T>(name, _form, condition));
if (validate != null)
{
field.SetValidation(validate);
}
return AddField(field);
}
public IFormBuilder<T> Field(string name, string prompt, ConditionalDelegate<T> condition = null, ValidateDelegate<T> validate = null)
{
var field = (condition == null ? new FieldReflector<T>(name, _form) : new Conditional<T>(name, _form, condition));
if (validate != null)
{
field.SetValidation(validate);
}
field.SetPrompt(new Prompt(prompt));
return AddField(field);
}
public IFormBuilder<T> Field(string name, Prompt prompt, ConditionalDelegate<T> condition = null, ValidateDelegate<T> validate = null)
{
var field = (condition == null ? new FieldReflector<T>(name, _form) : new Conditional<T>(name, _form, condition));
if (validate != null)
{
field.SetValidation(validate);
}
field.SetPrompt(prompt);
return AddField(field);
}
public IFormBuilder<T> Field(IField<T> field)
{
return AddField(field);
}
public IFormBuilder<T> AddRemainingFields(IEnumerable<string> exclude = null)
{
var exclusions = (exclude == null ? new string[0] : exclude.ToArray());
var paths = new List<string>();
FieldPaths(typeof(T), "", paths);
foreach (var path in paths)
{
if (!exclusions.Contains(path))
{
IField<T> field = _form._fields.Field(path);
if (field == null)
{
AddField(new FieldReflector<T>(path, _form));
}
}
}
return this;
}
public IFormBuilder<T> Confirm(string prompt, ConditionalDelegate<T> condition = null, IEnumerable<string> dependencies = null)
{
IFormBuilder<T> builder = this;
return builder.Confirm(new Prompt(prompt) { ChoiceFormat = "{1}", AllowDefault = BoolDefault.False }, condition, dependencies);
}
public IFormBuilder<T> Confirm(Prompt prompt, ConditionalDelegate<T> condition = null, IEnumerable<string> dependencies = null)
{
if (condition == null) condition = state => true;
if (dependencies == null)
{
// Default next steps go from previous field ignoring confirmations back to next confirmation
// Last field before confirmation
var end = _form._steps.Count();
while (end > 0)
{
if (_form._steps[end - 1].Type == StepType.Field)
{
break;
}
--end;
}
var start = end;
while (start > 0)
{
if (_form._steps[start - 1].Type == StepType.Confirm)
{
break;
}
--start;
}
var fields = new List<string>();
for (var i = start; i < end; ++i)
{
if (_form._steps[i].Type == StepType.Field)
{
fields.Add(_form._steps[i].Name);
}
}
dependencies = fields;
}
var confirmation = new Confirmation<T>(prompt, condition, dependencies, _form);
_form._fields.Add(confirmation);
_form._steps.Add(new ConfirmStep<T>(confirmation));
return this;
}
public IFormBuilder<T> Confirm(IFieldPrompt<T> prompt)
{
// TODO: Need to fill this in
return this;
}
public IFormBuilder<T> OnCompletionAsync(CompletionDelegate<T> callback)
{
_form._completion = callback;
return this;
}
private IFormBuilder<T> AddField(IField<T> field)
{
_form._fields.Add(field);
var step = new FieldStep<T>(field.Name, _form);
var stepIndex = this._form._steps.FindIndex(s => s.Name == field.Name);
if (stepIndex >= 0)
{
_form._steps[stepIndex] = step;
}
else
{
_form._steps.Add(step);
}
return this;
}
private Dictionary<TemplateUsage, int> _templateArgs = new Dictionary<TemplateUsage, int>
{
{TemplateUsage.Bool, 0 },
{ TemplateUsage.BoolHelp, 1},
{ TemplateUsage.Clarify, 1},
{ TemplateUsage.CurrentChoice, 0},
{ TemplateUsage.DateTime, 0},
{ TemplateUsage.DateTimeHelp, 2},
{ TemplateUsage.Double, 2},
{ TemplateUsage.DoubleHelp, 4},
{ TemplateUsage.EnumManyNumberHelp, 3},
{ TemplateUsage.EnumOneNumberHelp, 3},
{ TemplateUsage.EnumManyWordHelp, 3},
{ TemplateUsage.EnumOneWordHelp, 3},
{ TemplateUsage.EnumSelectOne, 0},
{ TemplateUsage.EnumSelectMany, 0},
{ TemplateUsage.Feedback, 1},
{ TemplateUsage.Help, 2},
{ TemplateUsage.HelpClarify, 2},
{ TemplateUsage.HelpConfirm, 2},
{ TemplateUsage.HelpNavigation, 2},
{ TemplateUsage.Integer, 2},
{ TemplateUsage.IntegerHelp, 4},
{ TemplateUsage.Navigation, 0},
{ TemplateUsage.NavigationCommandHelp, 1},
{ TemplateUsage.NavigationFormat, 0},
{ TemplateUsage.NavigationHelp, 2},
{ TemplateUsage.NoPreference, 0},
{ TemplateUsage.NotUnderstood, 1},
{ TemplateUsage.StatusFormat, 0},
{ TemplateUsage.String, 0},
{ TemplateUsage.StringHelp, 2},
{ TemplateUsage.Unspecified, 0},
};
private int TemplateArgs(TemplateUsage usage)
{
int args;
if (!_templateArgs.TryGetValue(usage, out args))
{
throw new ArgumentException("Missing template usage for validation");
}
return args;
}
private void Validate()
{
foreach (var step in _form._steps)
{
// Validate prompt
var annotation = step.Annotation;
var name = step.Type == StepType.Field ? step.Name : "";
foreach (var pattern in annotation.Patterns)
{
ValidatePattern(pattern, name, 5);
}
if (step.Type != StepType.Message)
{
foreach (TemplateUsage usage in Enum.GetValues(typeof(TemplateUsage)))
{
foreach (var pattern in step.Field.Template(usage).Patterns)
{
ValidatePattern(pattern, name, TemplateArgs(usage));
}
}
}
}
ValidatePattern(_form.Configuration.DefaultPrompt.ChoiceFormat, "", 2);
}
private void ValidatePattern(string pattern, string pathName, int maxArgs)
{
if (!Prompter<T>.ValidatePattern(_form, pattern, pathName, maxArgs))
{
throw new ArgumentException(string.Format("Illegal pattern: \"{0}\"", pattern));
}
}
internal static void FieldPaths(Type type, string path, List<string> paths)
{
var newPath = (path == "" ? path : path + ".");
foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance))
{
TypePaths(field.FieldType, newPath + field.Name, paths);
}
foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (property.CanRead && property.CanWrite)
{
TypePaths(property.PropertyType, newPath + property.Name, paths);
}
}
}
internal static void TypePaths(Type type, string path, List<string> paths)
{
if (type.IsClass)
{
if (type == typeof(string))
{
paths.Add(path);
}
else if (type.IsIEnumerable())
{
var elt = type.GetGenericElementType();
if (elt.IsEnum)
{
paths.Add(path);
}
else
{
// TODO: What to do about enumerations of things other than enums?
}
}
else
{
FieldPaths(type, path, paths);
}
}
else if (type.IsEnum)
{
paths.Add(path);
}
else if (type == typeof(bool))
{
paths.Add(path);
}
else if (type.IsIntegral())
{
paths.Add(path);
}
else if (type.IsDouble())
{
paths.Add(path);
}
else if (type.IsNullable() && type.IsValueType)
{
paths.Add(path);
}
else if (type == typeof(DateTime))
{
paths.Add(path);
}
}
}
}

Просмотреть файл

@ -474,7 +474,7 @@ namespace Microsoft.Bot.Builder.Form
if (step.Type == StepType.Confirm)
{
// Ensure all dependencies have values
foreach (var dependency in step.Dependencies())
foreach (var dependency in step.Dependencies)
{
var dstep = _form.Step(dependency);
var dstepi = _form.StepIndex(dstep);
@ -563,8 +563,8 @@ namespace Microsoft.Bot.Builder.Form
var navigation = new Prompter<T>(field.Template(TemplateUsage.NavigationCommandHelp), _form, null);
var active = (from istep in _form.Steps
where istep.Type == StepType.Field && istep.Active(state)
select istep.Field.Description());
var activeList = Language.BuildList(active, navigation.Annotation().Separator, navigation.Annotation().LastSeparator);
select istep.Field.FieldDescription);
var activeList = Language.BuildList(active, navigation.Annotation.Separator, navigation.Annotation.LastSeparator);
builder.Append("* ");
builder.Append(navigation.Prompt(state, "", activeList));
feedback = step.Help(state, form, builder.ToString());

Просмотреть файл

@ -1,393 +1,393 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license.
//
// Microsoft Bot Framework: http://botframework.com
//
// Bot Builder SDK Github:
// https://github.com/Microsoft/BotBuilder
//
// Copyright (c) Microsoft Corporation
// All rights reserved.
//
// MIT License:
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.Bot.Builder.Form.Advanced
{
/// <summary>
/// Interface that defines basic access to a field.
/// </summary>
/// <typeparam name="T">The form state that is read or written to.</typeparam>
public interface IFieldState<T>
{
/// <summary>
/// Get this field value from form state.
/// </summary>
/// <param name="state">Form state to get field value from.</param>
/// <returns>Current value found in state.</returns>
object GetValue(T state);
/// <summary>
/// Set this field value in form state.
/// </summary>
/// <param name="state">Form state to set field value in.</param>
/// <param name="value">New value.</param>
void SetValue(T state, object value);
/// <summary>
/// Test to see if the field value form state has a value.
/// </summary>
/// <param name="state">Form state to check.</param>
/// <returns>True if value is unknown.</returns>
/// <remarks>
/// For value types (numbers, bools, date time) a value is unknown only if the field is nullable and it is null.
/// For enum based values (both simple and enumerated) they can also be nullable or the 0 enum value if not nullable.
/// For non value types like string the test is to see if the field is actually null.
/// </remarks>
bool IsUnknown(T state);
/// <summary>
/// Set this field value in form state to unknown.
/// </summary>
/// <param name="state">Form state with field value to set to unknown.</param>
/// <remarks>
/// For value types (numbers, bools, date time) the value is set to null if nullable.
/// For enum types it is set to null if nullable or 0 if not.
/// For non value types like string set the value to null.
/// </remarks>
void SetUnknown(T state);
/// <summary>
/// Test to see if field is optional which means that an unknown value is legal.
/// </summary>
/// <returns>True if field is optional.</returns>
bool Optional();
/// <summary>
/// Test to see if field is nullable.
/// </summary>
/// <returns>True if field is nullable.</returns>
bool IsNullable();
/// <summary>
/// Limits of numeric values.
/// </summary>
/// <param name="min">Minimum possible value.</param>
/// <param name="max">Maximum possible value.</param>
/// <returns>True if limits limit the underlying data type.</returns>
/// <remarks>
/// This reflects the result of setting <see cref="Numeric" limits on the possible values./></remarks>
bool Limits(out double min, out double max);
/// <summary>
/// Returns the other fields this one depends on.
/// </summary>
/// <returns>List of field names this one depends on.</returns>
/// <remarks>This is mainly useful for <see cref="Confirmation"/> fields.</remarks>
IEnumerable<string> Dependencies();
}
/// <summary>
/// The role the field plays in a form.
/// </summary>
public enum FieldRole {
/// <summary>
/// Field is used to get a value to set in the form state.
/// </summary>
/// <remarks>This is the kind of field generated by <see cref="IForm<T>.Field"/>.</remarks>
Value,
/// <summary>
/// Field is used to confirm some settings during the dialog.
/// </summary>
/// <remarks>
/// This is the kind of field generated by <see cref="IForm<T>.Confirm"/>.
/// </remarks>
Confirm };
/// <summary>
/// Describe the information displayed about a field and its values.
/// </summary>
/// <remarks>
/// Throughout this class Description refers to the name of a field or a value
/// whereas "terms" tell what people can type to match the field or terms in it.
/// When generating terms it is a good idea to include anything that might be reasonable
/// for someone to type. The form dialog itself will help clarify any ambiguity. One
/// way to do this is to use <see cref="Terms.MatchPhrase"/> which ensures that <see cref="Language.GenerateTerms"/>
/// is called on your base terms.
/// </remarks>
public interface IFieldDescription
{
/// <summary>
/// Role field plays in a form.
/// </summary>
/// <returns>Role field plays in form.</returns>
FieldRole Role();
/// <summary>
/// Decription of the field itself.
/// </summary>
/// <returns>Field description.</returns>
/// <remarks>
/// This is the value that will be generated in \ref patterns by {&amp;} or choices. {||}.
/// </remarks>
string Description();
/// <summary>
/// Terms for matching this field.
/// </summary>
/// <returns>List of term regex for matching the field name.</returns>
IEnumerable<string> Terms();
/// <summary>
/// Return the string describing a specific value.
/// </summary>
/// <param name="value">Value being described.</param>
/// <returns>String describing value.</returns>
string ValueDescription(object value);
/// <summary>
/// Return all possible value descriptions in order to support enumeration.
/// </summary>
/// <returns>All possible value descriptions.</returns>
IEnumerable<string> ValueDescriptions();
/// <summary>
/// Given a value return terms that can be used in a dialog to match the object.
/// </summary>
/// <param name="value">Value that would result from a match.</param>
/// <returns>Enumeration of regex.</returns>
IEnumerable<string> Terms(object value);
/// <summary>
/// All possible values or null if it is a data type like number.
/// </summary>
/// <returns>All possible values.</returns>
IEnumerable<object> Values();
/// <summary>
/// Are multiple matches allowed.
/// </summary>
/// <returns>True if more than one value is allowed.</returns>
/// <remarks>This is true is you have a list of enumerated values.</remarks>
bool AllowsMultiple();
/// <summary>
/// Allow the default value as an option.
/// </summary>
/// <returns>True if default values are allowed.</returns>
bool AllowDefault();
/// <summary>
/// Allow user input to match numbers shown with enumerated choices.
/// </summary>
/// <returns>True if numbers are allowed as input.</returns>
bool AllowNumbers();
}
/// <summary>
/// Direction for next step.
/// </summary>
/// <remarks>
/// As each step in a form completes, the step can determine the next step to take.
/// Usually this is just to move onto the next active, uncompleted step, but you can
/// also move back or present a list of choices to the user.
/// A step is active if its <see cref="ConditionDelegate"/> returns true on the current state.
/// A step is ready if it has not already been successfully completed.
/// </remarks>
public enum StepDirection
{
/// <summary>
/// The form is complete and <see cref="IForm<T>.OnCompletion"/> should be called.
/// </summary>
Complete,
/// <summary>
/// Move to a named step. If there is more than one name, the user will be asked to choose.
/// </summary>
Named,
/// <summary>
/// Move to the next step that is Active() and uncompleted.
/// </summary>
Next,
/// <summary>
/// Move to the previously executed step.
/// </summary>
Previous,
/// <summary>
/// Quit the form and return failure to the parent dialog.
/// </summary>
Quit,
/// <summary>
/// Reset the form to start over.
/// </summary>
Reset
};
/// <summary>
/// Next step to take.
/// </summary>
[Serializable]
public class NextStep
{
/// <summary>
/// By default move on to the next active, uncompleted step.
/// </summary>
public NextStep()
{
Direction = StepDirection.Next;
}
/// <summary>
/// Move as specified in direction.
/// </summary>
/// <param name="direction">What step to do next.</param>
public NextStep(StepDirection direction)
{
Direction = direction;
}
/// <summary>
/// Ask the user which of the fields to move to next.
/// </summary>
/// <param name="names">Enumeration of possible next steps.</param>
public NextStep(IEnumerable<string> names)
{
Direction = StepDirection.Named;
Names = names.ToArray();
}
/// <summary>
/// Direction for next step.
/// </summary>
public StepDirection Direction;
/// <summary>
/// If this is a named step, one or more named steps to move to. If there are more than one, the user will choose.
/// </summary>
public string[] Names;
}
/// <summary>
/// This provides control information about a field.
/// </summary>
/// <typeparam name="T">Form state that is being completed.</typeparam>
public interface IFieldPrompt<T>
{
/// <summary>
/// Test to see if field is currently active based on the current state.
/// </summary>
/// <returns>True if field is active.</returns>
/// <remarks>
/// One way to control this is to supply a <see cref="ConditionalDelegate"/> to the
/// <see cref="IForm<T>.Field"/> or <see cref="IForm<T>.Confirm"/> steps.
/// </remarks>
bool Active(T state);
/// <summary>
/// Return a template for building a prompt.
/// </summary>
/// <param name="usage">Kind of template we are looking for.</param>
/// <returns>NULL if no template, otherwise a template annotation.</returns>
Template Template(TemplateUsage usage);
/// <summary>
/// Return the prompt associated with a field.
/// </summary>
/// <returns>A prompt and recognizer packaged together.</returns>
IPrompt<T> Prompt();
/// <summary>
/// Validate value to be set on state and return feedback if not valid.
/// </summary>
/// <param name="state">State before setting value.</param>
/// <param name="value">Value to be set in field.</param>
/// <returns>Null if OK, otherwise feedback on what should change.</returns>
/// <remarks>
/// One way to control this is to supply a <see cref="ValidationDelegate"/> to the
/// <see cref="IForm<T>.Field"/> or <see cref="IForm<T>.Confirm"/> steps.
/// </remarks>
Task<string> ValidateAsync(T state, object value);
/// <summary>
/// Return the help description for this field.
/// </summary>
/// <returns>The prompt to use for generating help.</returns>
/// <remarks>
/// Help is a mixture of field specific help, what a recognizer understands and available commands.
/// </remarks>
IPrompt<T> Help();
/// <summary>
/// Next step to execute.
/// </summary>
/// <param name="value">Value in response to prompt.</param>
/// <param name="state">Current form state.</param>
/// <returns>Next step to execute.</returns>
NextStep Next(object value, T state);
}
/// <summary>
/// Interface for all the information about a specific field.
/// </summary>
/// <typeparam name="T">Form state interface applies to.</typeparam>
public interface IField<T> : IFieldState<T>, IFieldDescription, IFieldPrompt<T>
{
/// <summary>
/// Name of this field.
/// </summary>
/// <returns>Name of this field.</returns>
/// <remarks>
/// For a value field this is the path in the form state that leads to the value being filled in.
/// For a confirm field this is a randomly generated name.
/// </remarks>
string Name { get; }
/// <summary>
/// Form that owns this field
/// </summary>
IForm<T> Form { get; }
}
/// <summary>
/// Interface to track all of the fields in a form.
/// </summary>
/// <typeparam name="T"></typeparam>
public interface IFields<T> : IEnumerable<IField<T>>
{
/// <summary>
/// Return a specific field or null if not present.
/// </summary>
/// <param name="name">Name of field to find.</param>
/// <returns>Field description for name or null.</returns>
IField<T> Field(string name);
}
}
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license.
//
// Microsoft Bot Framework: http://botframework.com
//
// Bot Builder SDK Github:
// https://github.com/Microsoft/BotBuilder
//
// Copyright (c) Microsoft Corporation
// All rights reserved.
//
// MIT License:
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.Bot.Builder.Form.Advanced
{
/// <summary>
/// Interface that defines basic access to a field.
/// </summary>
/// <typeparam name="T">The form state that is read or written to.</typeparam>
public interface IFieldState<T>
{
/// <summary>
/// Get this field value from form state.
/// </summary>
/// <param name="state">Form state to get field value from.</param>
/// <returns>Current value found in state.</returns>
object GetValue(T state);
/// <summary>
/// Set this field value in form state.
/// </summary>
/// <param name="state">Form state to set field value in.</param>
/// <param name="value">New value.</param>
void SetValue(T state, object value);
/// <summary>
/// Test to see if the field value form state has a value.
/// </summary>
/// <param name="state">Form state to check.</param>
/// <returns>True if value is unknown.</returns>
/// <remarks>
/// For value types (numbers, bools, date time) a value is unknown only if the field is nullable and it is null.
/// For enum based values (both simple and enumerated) they can also be nullable or the 0 enum value if not nullable.
/// For non value types like string the test is to see if the field is actually null.
/// </remarks>
bool IsUnknown(T state);
/// <summary>
/// Set this field value in form state to unknown.
/// </summary>
/// <param name="state">Form state with field value to set to unknown.</param>
/// <remarks>
/// For value types (numbers, bools, date time) the value is set to null if nullable.
/// For enum types it is set to null if nullable or 0 if not.
/// For non value types like string set the value to null.
/// </remarks>
void SetUnknown(T state);
/// <summary>
/// Test to see if field is optional which means that an unknown value is legal.
/// </summary>
/// <returns>True if field is optional.</returns>
bool Optional { get; }
/// <summary>
/// Test to see if field is nullable.
/// </summary>
/// <returns>True if field is nullable.</returns>
bool IsNullable { get; }
/// <summary>
/// Limits of numeric values.
/// </summary>
/// <param name="min">Minimum possible value.</param>
/// <param name="max">Maximum possible value.</param>
/// <returns>True if limits limit the underlying data type.</returns>
/// <remarks>
/// This reflects the result of setting <see cref="Numeric" limits on the possible values./></remarks>
bool Limits(out double min, out double max);
/// <summary>
/// Returns the other fields this one depends on.
/// </summary>
/// <returns>List of field names this one depends on.</returns>
/// <remarks>This is mainly useful for <see cref="Confirmation"/> fields.</remarks>
IEnumerable<string> Dependencies { get; }
}
/// <summary>
/// The role the field plays in a form.
/// </summary>
public enum FieldRole {
/// <summary>
/// Field is used to get a value to set in the form state.
/// </summary>
/// <remarks>This is the kind of field generated by <see cref="IForm<T>.Field"/>.</remarks>
Value,
/// <summary>
/// Field is used to confirm some settings during the dialog.
/// </summary>
/// <remarks>
/// This is the kind of field generated by <see cref="IForm<T>.Confirm"/>.
/// </remarks>
Confirm };
/// <summary>
/// Describe the information displayed about a field and its values.
/// </summary>
/// <remarks>
/// Throughout this class Description refers to the name of a field or a value
/// whereas "terms" tell what people can type to match the field or terms in it.
/// When generating terms it is a good idea to include anything that might be reasonable
/// for someone to type. The form dialog itself will help clarify any ambiguity. One
/// way to do this is to use <see cref="Terms.MatchPhrase"/> which ensures that <see cref="Language.GenerateTerms"/>
/// is called on your base terms.
/// </remarks>
public interface IFieldDescription
{
/// <summary>
/// Role field plays in a form.
/// </summary>
/// <returns>Role field plays in form.</returns>
FieldRole Role { get; }
/// <summary>
/// Decription of the field itself.
/// </summary>
/// <returns>Field description.</returns>
/// <remarks>
/// This is the value that will be generated in \ref patterns by {&amp;} or choices. {||}.
/// </remarks>
string FieldDescription { get; }
/// <summary>
/// Terms for matching this field.
/// </summary>
/// <returns>List of term regex for matching the field name.</returns>
IEnumerable<string> FieldTerms { get; }
/// <summary>
/// Return the string describing a specific value.
/// </summary>
/// <param name="value">Value being described.</param>
/// <returns>String describing value.</returns>
string ValueDescription(object value);
/// <summary>
/// Return all possible value descriptions in order to support enumeration.
/// </summary>
/// <returns>All possible value descriptions.</returns>
IEnumerable<string> ValueDescriptions { get; }
/// <summary>
/// Given a value return terms that can be used in a dialog to match the object.
/// </summary>
/// <param name="value">Value that would result from a match.</param>
/// <returns>Enumeration of regex.</returns>
IEnumerable<string> Terms(object value);
/// <summary>
/// All possible values or null if it is a data type like number.
/// </summary>
/// <returns>All possible values.</returns>
IEnumerable<object> Values { get; }
/// <summary>
/// Are multiple matches allowed.
/// </summary>
/// <returns>True if more than one value is allowed.</returns>
/// <remarks>This is true is you have a list of enumerated values.</remarks>
bool AllowsMultiple { get; }
/// <summary>
/// Allow the default value as an option.
/// </summary>
/// <returns>True if default values are allowed.</returns>
bool AllowDefault { get; }
/// <summary>
/// Allow user input to match numbers shown with enumerated choices.
/// </summary>
/// <returns>True if numbers are allowed as input.</returns>
bool AllowNumbers { get; }
}
/// <summary>
/// Direction for next step.
/// </summary>
/// <remarks>
/// As each step in a form completes, the step can determine the next step to take.
/// Usually this is just to move onto the next active, uncompleted step, but you can
/// also move back or present a list of choices to the user.
/// A step is active if its <see cref="ConditionDelegate"/> returns true on the current state.
/// A step is ready if it has not already been successfully completed.
/// </remarks>
public enum StepDirection
{
/// <summary>
/// The form is complete and <see cref="IForm<T>.OnCompletion"/> should be called.
/// </summary>
Complete,
/// <summary>
/// Move to a named step. If there is more than one name, the user will be asked to choose.
/// </summary>
Named,
/// <summary>
/// Move to the next step that is Active() and uncompleted.
/// </summary>
Next,
/// <summary>
/// Move to the previously executed step.
/// </summary>
Previous,
/// <summary>
/// Quit the form and return failure to the parent dialog.
/// </summary>
Quit,
/// <summary>
/// Reset the form to start over.
/// </summary>
Reset
};
/// <summary>
/// Next step to take.
/// </summary>
[Serializable]
public class NextStep
{
/// <summary>
/// By default move on to the next active, uncompleted step.
/// </summary>
public NextStep()
{
Direction = StepDirection.Next;
}
/// <summary>
/// Move as specified in direction.
/// </summary>
/// <param name="direction">What step to do next.</param>
public NextStep(StepDirection direction)
{
Direction = direction;
}
/// <summary>
/// Ask the user which of the fields to move to next.
/// </summary>
/// <param name="names">Enumeration of possible next steps.</param>
public NextStep(IEnumerable<string> names)
{
Direction = StepDirection.Named;
Names = names.ToArray();
}
/// <summary>
/// Direction for next step.
/// </summary>
public StepDirection Direction;
/// <summary>
/// If this is a named step, one or more named steps to move to. If there are more than one, the user will choose.
/// </summary>
public string[] Names;
}
/// <summary>
/// This provides control information about a field.
/// </summary>
/// <typeparam name="T">Form state that is being completed.</typeparam>
public interface IFieldPrompt<T>
{
/// <summary>
/// Test to see if field is currently active based on the current state.
/// </summary>
/// <returns>True if field is active.</returns>
/// <remarks>
/// One way to control this is to supply a <see cref="ConditionalDelegate"/> to the
/// <see cref="IForm<T>.Field"/> or <see cref="IForm<T>.Confirm"/> steps.
/// </remarks>
bool Active(T state);
/// <summary>
/// Return a template for building a prompt.
/// </summary>
/// <param name="usage">Kind of template we are looking for.</param>
/// <returns>NULL if no template, otherwise a template annotation.</returns>
Template Template(TemplateUsage usage);
/// <summary>
/// Return the prompt associated with a field.
/// </summary>
/// <returns>A prompt and recognizer packaged together.</returns>
IPrompt<T> Prompt();
/// <summary>
/// Validate value to be set on state and return feedback if not valid.
/// </summary>
/// <param name="state">State before setting value.</param>
/// <param name="value">Value to be set in field.</param>
/// <returns>Null if OK, otherwise feedback on what should change.</returns>
/// <remarks>
/// One way to control this is to supply a <see cref="ValidationDelegate"/> to the
/// <see cref="IForm<T>.Field"/> or <see cref="IForm<T>.Confirm"/> steps.
/// </remarks>
Task<string> ValidateAsync(T state, object value);
/// <summary>
/// Return the help description for this field.
/// </summary>
/// <returns>The prompt to use for generating help.</returns>
/// <remarks>
/// Help is a mixture of field specific help, what a recognizer understands and available commands.
/// </remarks>
IPrompt<T> Help();
/// <summary>
/// Next step to execute.
/// </summary>
/// <param name="value">Value in response to prompt.</param>
/// <param name="state">Current form state.</param>
/// <returns>Next step to execute.</returns>
NextStep Next(object value, T state);
}
/// <summary>
/// Interface for all the information about a specific field.
/// </summary>
/// <typeparam name="T">Form state interface applies to.</typeparam>
public interface IField<T> : IFieldState<T>, IFieldDescription, IFieldPrompt<T>
{
/// <summary>
/// Name of this field.
/// </summary>
/// <returns>Name of this field.</returns>
/// <remarks>
/// For a value field this is the path in the form state that leads to the value being filled in.
/// For a confirm field this is a randomly generated name.
/// </remarks>
string Name { get; }
/// <summary>
/// Form that owns this field
/// </summary>
IForm<T> Form { get; }
}
/// <summary>
/// Interface to track all of the fields in a form.
/// </summary>
/// <typeparam name="T"></typeparam>
public interface IFields<T> : IEnumerable<IField<T>>
{
/// <summary>
/// Return a specific field or null if not present.
/// </summary>
/// <param name="name">Name of field to find.</param>
/// <returns>Field description for name or null.</returns>
IField<T> Field(string name);
}
}

Просмотреть файл

@ -80,7 +80,7 @@ namespace Microsoft.Bot.Builder.Form
internal static IRecognize<T> BuildCommandRecognizer<T>(this IForm<T> form) where T : class
{
var field = new Field<T>("__commands__", FieldRole.Value, form);
field.Prompt(new Prompt(""));
field.SetPrompt(new Prompt(""));
foreach (var entry in form.Configuration.Commands)
{
field.AddDescription(entry.Key, entry.Value.Description);
@ -88,10 +88,10 @@ namespace Microsoft.Bot.Builder.Form
}
foreach (var nav in form.Fields)
{
var fterms = nav.Terms();
var fterms = nav.FieldTerms;
if (fterms != null)
{
field.AddDescription(nav.Name, nav.Description());
field.AddDescription(nav.Name, nav.FieldDescription);
field.AddTerms(nav.Name, fterms.ToArray());
}
}

Просмотреть файл

@ -32,6 +32,7 @@
//
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.Bot.Builder.Form.Advanced;
@ -185,5 +186,169 @@ namespace Microsoft.Bot.Builder.Form
/// to the parent dialog.
/// </remarks>
IFormBuilder<T> OnCompletionAsync(CompletionDelegate<T> callback);
}
}
/// <summary>
/// Default values for the form.
/// </summary>
/// <remarks>
/// These defaults can all be overriden when you create a form and before you add steps.
/// </remarks>
public class FormConfiguration
{
/// <summary>
/// Default prompt and template format settings.
/// </summary>
/// <remarks>
/// When you specify a <see cref="Prompt"/> or <see cref="Template"/>, any format
/// value you do not specify will come from this default.
/// </remarks>
public Prompt DefaultPrompt = new Prompt("")
{
AllowDefault = BoolDefault.True,
ChoiceStyle = ChoiceStyleOptions.Auto,
FieldCase = CaseNormalization.Lower,
Feedback = FeedbackOptions.Auto,
ChoiceFormat = "{0}. {1}",
LastSeparator = ", and ",
Separator = ", ",
ValueCase = CaseNormalization.InitialUpper
};
/// <summary>
/// Enumeration of strings for interpreting a user response as setting an optional field to be unspecified.
/// </summary>
/// <remarks>
/// The first string is also used to describe not having a preference for an optional field.
/// </remarks>
public string[] NoPreference = new string[] { "No Preference", "no", "none", "I don'?t care" };
/// <summary>
/// Enumeration of strings for interpreting a user response as asking for the current value.
/// </summary>
/// <remarks>
/// The first value is also used to describe the option of keeping the current value.
/// </remarks>
public string[] CurrentChoice = new string[] { "Current Choice", "current" };
/// <summary>
/// Enumeration of values for a "yes" response for boolean fields or confirmations.
/// </summary>
public string[] Yes = new string[] { "Yes", "yes", "y", "sure", "ok" };
/// <summary>
/// Enumeration of values for a "no" response for boolean fields or confirmations.
/// </summary>
public string[] No = new string[] { "No", "n" };
/// <summary>
/// Default templates to use if not override on the class or field level.
/// </summary>
public List<Template> Templates = new List<Template>
{
new Template(TemplateUsage.Bool, "Would you like a {&}? {||}"),
new Template(TemplateUsage.BoolHelp, "Please enter 'yes' or 'no'{?, {0}}."),
// {0} is term being clarified
new Template(TemplateUsage.Clarify, "By \"{0}\" {&} did you mean {||}"),
new Template(TemplateUsage.CurrentChoice, "(current choice: {})"),
new Template(TemplateUsage.DateTime, "Please enter a date and time for {&} {||}"),
// {0} is current choice, {1} is no preference
new Template(TemplateUsage.DateTimeHelp, "Please enter a date or time expression like 'Monday' or 'July 3rd'{?, {0}}{?, {1}}."),
// {0} is min and {1} is max.
new Template(TemplateUsage.Double, "Please enter a number {?between {0:F1} and {1:F1}} for {&} {||}") { ChoiceFormat = "{1}" },
// {0} is current choice, {1} is no preference
// {2} is min and {3} is max
new Template(TemplateUsage.DoubleHelp, "Please enter a number{? between {2:F1} and {3:F1}}{?, {0}}{?, {1}}."),
// {0} is min, {1} is max and {2} are enumerated descriptions
new Template(TemplateUsage.EnumManyNumberHelp, "You can enter one or more numbers {0}-{1} or words from the descriptions. ({2})"),
new Template(TemplateUsage.EnumOneNumberHelp, "You can enter a number {0}-{1} or words from the descriptions. ({2})"),
// {2} are the words people can type
new Template(TemplateUsage.EnumManyWordHelp, "You can enter in one or more selections from the descriptions. ({2})"),
new Template(TemplateUsage.EnumOneWordHelp, "You can enter in any words from the descriptions. ({2})"),
new Template(TemplateUsage.EnumSelectOne, "Please select a {&} {||}"),
new Template(TemplateUsage.EnumSelectMany, "Please select one or more {&} {||}"),
// {0} is the not understood term
new Template(TemplateUsage.Feedback, "For {&} I understood {}. {?\"{0}\" is not an option.}"),
// For {0} is recognizer help and {1} is command help.
new Template(TemplateUsage.Help, "You are filling in the {&} field. Possible responses:\n{0}\n{1}"),
new Template(TemplateUsage.HelpClarify, "You are clarifying a {&} value. Possible responses:\n{0}\n{1}"),
new Template(TemplateUsage.HelpConfirm, "Please answer the question. Possible responses:\n{0}\n{1}"),
new Template(TemplateUsage.HelpNavigation, "Choose what field to change. Possible responses:\n{0}\n{1}"),
// {0} is min and {1} is max if present
new Template(TemplateUsage.Integer, "Please enter a number{? between {0} and {1}} for {&} {||}") { ChoiceFormat = "{1}" },
// {0} is current choice, {1} is no preference
// {2} is min and {3} is max
new Template(TemplateUsage.IntegerHelp, "You can enter a number{? between {2} and {3}}{?, {0}}{?, {1}}."),
new Template(TemplateUsage.Navigation, "What do you want to change? {||}") { FieldCase = CaseNormalization.None },
// {0} is list of field names.
new Template(TemplateUsage.NavigationCommandHelp, "You can switch to another field by entering its name. ({0})."),
new Template(TemplateUsage.NavigationFormat, "{&}({})") {FieldCase = CaseNormalization.None },
// {0} is min, {1} is max
new Template(TemplateUsage.NavigationHelp, "Choose {?a number from {0}-{1}, or} a field name."),
new Template(TemplateUsage.NoPreference, "No Preference"),
// {0} is the term that is not understood
new Template(TemplateUsage.NotUnderstood, @"""{0}"" is not a {&} option."),
new Template(TemplateUsage.StatusFormat, "{&}: {}") {FieldCase = CaseNormalization.None },
new Template(TemplateUsage.String, "Please enter {&} {||}") { ChoiceFormat = "{1}" },
// {0} is current choice, {1} is no preference
new Template(TemplateUsage.StringHelp, "You can enter anything{?, {0}}{?, {1}}."),
new Template(TemplateUsage.Unspecified, "Unspecified")
};
/// <summary>
/// Definitions of the built-in commands.
/// </summary>
public Dictionary<FormCommand, CommandDescription> Commands = new Dictionary<FormCommand, CommandDescription>()
{
{FormCommand.Backup, new CommandDescription("Backup", new string[] {"backup", "go back", "back" },
"Back: Go back to the previous question.") },
{FormCommand.Help, new CommandDescription("Help", new string[] { "help", "choices", @"\?" },
"Help: Show the kinds of responses you can enter.") },
{FormCommand.Quit, new CommandDescription("Quit", new string[] { "quit", "stop", "finish", "goodbye", "good bye"},
"Quit: Quit the form without completing it.") },
{FormCommand.Reset, new CommandDescription("Start over", new string[] { "start over", "reset", "clear" },
"Reset: Start over filling in the form. (With defaults of your previous entries.)" ) },
{FormCommand.Status, new CommandDescription("status", new string[] {"status", "progress", "so far" },
"Status: Show your progress in filling in the form so far.") }
};
/// <summary>
/// Look up a particular template.
/// </summary>
/// <param name="usage">Desired template.</param>
/// <returns>Matching template.</returns>
public Template Template(TemplateUsage usage)
{
Template result = null;
foreach (var template in Templates)
{
if (template.Usage == usage)
{
result = template;
break;
}
}
Debug.Assert(result != null);
return result;
}
};
}

Просмотреть файл

@ -153,166 +153,5 @@ namespace Microsoft.Bot.Builder.Form
Help = help;
}
}
/// <summary>
/// Default values for the form.
/// </summary>
/// <remarks>
/// These defaults can all be overriden when you create a form and before you add steps.
/// </remarks>
public class FormConfiguration
{
/// <summary>
/// Default prompt and template format settings.
/// </summary>
/// <remarks>
/// When you specify a <see cref="Prompt"/> or <see cref="Template"/>, any format
/// value you do not specify will come from this default.
/// </remarks>
public Prompt DefaultPrompt = new Prompt("")
{
AllowDefault = BoolDefault.True,
ChoiceStyle = ChoiceStyleOptions.Auto,
FieldCase = CaseNormalization.Lower,
Feedback = FeedbackOptions.Auto,
ChoiceFormat = "{0}. {1}",
LastSeparator = ", and ",
Separator = ", ",
ValueCase = CaseNormalization.InitialUpper
};
/// <summary>
/// Enumeration of strings for interpreting a user response as setting an optional field to be unspecified.
/// </summary>
/// <remarks>
/// The first string is also used to describe not having a preference for an optional field.
/// </remarks>
public string[] NoPreference = new string[] { "No Preference", "no", "none", "I don'?t care" };
/// <summary>
/// Enumeration of strings for interpreting a user response as asking for the current value.
/// </summary>
/// <remarks>
/// The first value is also used to describe the option of keeping the current value.
/// </remarks>
public string[] CurrentChoice = new string[] { "Current Choice", "current" };
/// <summary>
/// Enumeration of values for a "yes" response for boolean fields or confirmations.
/// </summary>
public string[] Yes = new string[] { "Yes", "yes", "y", "sure", "ok" };
/// <summary>
/// Enumeration of values for a "no" response for boolean fields or confirmations.
/// </summary>
public string[] No = new string[] { "No", "n" };
/// <summary>
/// Default templates to use if not override on the class or field level.
/// </summary>
public List<Template> Templates = new List<Template>
{
new Template(TemplateUsage.Bool, "Would you like a {&}? {||}"),
new Template(TemplateUsage.BoolHelp, "Please enter 'yes' or 'no'{?, {0}}."),
// {0} is term being clarified
new Template(TemplateUsage.Clarify, "By \"{0}\" {&} did you mean {||}"),
new Template(TemplateUsage.CurrentChoice, "(current choice: {})"),
new Template(TemplateUsage.DateTime, "Please enter a date and time for {&} {||}"),
// {0} is current choice, {1} is no preference
new Template(TemplateUsage.DateTimeHelp, "Please enter a date or time expression like 'Monday' or 'July 3rd'{?, {0}}{?, {1}}."),
new Template(TemplateUsage.Double, "Please enter a number for {&} {||}") { ChoiceFormat = "{1}" },
// {0} is current choice, {1} is no preference
// {2} is min and {3} is max
new Template(TemplateUsage.DoubleHelp, "Please enter a number{? between {2:F1} and {3:F1}}{?, {0}}{?, {1}}."),
// {0} is min, {1} is max and {2} are enumerated descriptions
new Template(TemplateUsage.EnumOneNumberHelp, "You can enter a number {0}-{1} or words from the descriptions. ({2})"),
new Template(TemplateUsage.EnumManyNumberHelp, "You can enter one or more numbers {0}-{1} or words from the descriptions. ({2})"),
// {0} are the words people can type
new Template(TemplateUsage.EnumOneWordHelp, "You can enter in any words from the descriptions. ({2})"),
new Template(TemplateUsage.EnumManyWordHelp, "You can enter in one or more selections from the descriptions. ({2})"),
new Template(TemplateUsage.EnumSelectOne, "Please select a {&} {||}"),
new Template(TemplateUsage.EnumSelectMany, "Please select one or more {&} {||}"),
// {0} is the not understood term
new Template(TemplateUsage.Feedback, "For {&} I understood {}. {?\"{0}\" is not an option.}"),
// For {0} is recognizer help and {1} is command help.
new Template(TemplateUsage.Help, "You are filling in the {&} field. Possible responses:\n{0}\n{1}"),
new Template(TemplateUsage.HelpConfirm, "Please answer the question. Possible responses:\n{0}\n{1}"),
new Template(TemplateUsage.HelpClarify, "You are clarifying a {&} value. Possible responses:\n{0}\n{1}"),
new Template(TemplateUsage.HelpNavigation, "Choose what field to change. Possible responses:\n{0}\n{1}"),
// {0} is min and {1} is max if present
new Template(TemplateUsage.Integer, "Please enter a number{? between {0} and {1}} for {&} {||}") { ChoiceFormat = "{1}" },
// {0} is current choice, {1} is no preference
// {2} is min and {3} is max
new Template(TemplateUsage.IntegerHelp, "You can enter a number{? between {2} and {3}}{?, {0}}{?, {1}}."),
new Template(TemplateUsage.Navigation, "What do you want to change? {||}") { FieldCase = CaseNormalization.None },
// {0} is list of field names.
new Template(TemplateUsage.NavigationCommandHelp, "You can switch to another field by entering its name. ({0})."),
new Template(TemplateUsage.NavigationFormat, "{&}({})") {FieldCase = CaseNormalization.None },
new Template(TemplateUsage.NavigationHelp, "Choose {?a number from {0}-{1}, or} a field name."),
new Template(TemplateUsage.NoPreference, "No Preference"),
// {0} is the term that is not understood
new Template(TemplateUsage.NotUnderstood, @"""{0}"" is not a {&} option."),
new Template(TemplateUsage.StatusFormat, "{&}: {}") {FieldCase = CaseNormalization.None },
new Template(TemplateUsage.String, "Please enter {&} {||}") { ChoiceFormat = "{1}" },
// {0} is current choice, {1} is no preference
new Template(TemplateUsage.StringHelp, "You can enter anything{?, {0}}{?, {1}}."),
new Template(TemplateUsage.Unspecified, "Unspecified")
};
/// <summary>
/// Definitions of the built-in commands.
/// </summary>
public Dictionary<FormCommand, CommandDescription> Commands = new Dictionary<FormCommand, CommandDescription>()
{
{FormCommand.Backup, new CommandDescription("Backup", new string[] {"backup", "go back", "back" },
"Back: Go back to the previous question.") },
{FormCommand.Help, new CommandDescription("Help", new string[] { "help", "choices", @"\?" },
"Help: Show the kinds of responses you can enter.") },
{FormCommand.Quit, new CommandDescription("Quit", new string[] { "quit", "stop", "finish", "goodbye", "good bye"},
"Quit: Quit the form without completing it.") },
{FormCommand.Reset, new CommandDescription("Start over", new string[] { "start over", "reset", "clear" },
"Reset: Start over filling in the form. (With defaults of your previous entries.)" ) },
{FormCommand.Status, new CommandDescription("status", new string[] {"status", "progress", "so far" },
"Status: Show your progress in filling in the form so far.") }
};
/// <summary>
/// Look up a particular template.
/// </summary>
/// <param name="usage">Desired template.</param>
/// <returns>Matching template.</returns>
public Template Template(TemplateUsage usage)
{
Template result = null;
foreach(var template in Templates)
{
if (template.Usage == usage)
{
result = template;
break;
}
}
Debug.Assert(result != null);
return result;
}
};
}
}

Просмотреть файл

@ -53,7 +53,7 @@ namespace Microsoft.Bot.Builder.Form.Advanced
/// Description of the prompt and how to generate it.
/// </summary>
/// <returns>Attribute describing how to generate prompt.</returns>
TemplateBase Annotation();
TemplateBase Annotation { get; }
/// <summary>
/// Return string to send to user.
@ -87,9 +87,12 @@ namespace Microsoft.Bot.Builder.Form.Advanced
_recognizer = recognizer;
}
public TemplateBase Annotation()
public TemplateBase Annotation
{
return _annotation;
get
{
return _annotation;
}
}
public string Prompt(T state, string pathName, params object[] args)
@ -100,7 +103,7 @@ namespace Microsoft.Bot.Builder.Form.Advanced
{
var field = _form.Fields.Field(pathName);
currentChoice = field.Template(TemplateUsage.CurrentChoice).Pattern();
if (field.Optional())
if (field.Optional)
{
noValue = field.Template(TemplateUsage.NoPreference).Pattern();
}
@ -113,6 +116,62 @@ namespace Microsoft.Bot.Builder.Form.Advanced
return (response == null ? "" : _spacesPunc.Replace(_spaces.Replace(Language.ANormalization(response), "$1 "), "$1"));
}
public static bool ValidatePattern(IForm<T> form, string pattern, string pathName, int argLimit = 0)
{
bool ok = true;
var fields = form.Fields;
foreach (Match match in _args.Matches(pattern))
{
var expr = match.Groups[1].Value.Trim();
int numeric;
if (expr == "||")
{
ok = true;
}
else if (expr.StartsWith("&"))
{
var name = expr.Substring(1);
if (name == "") name = pathName;
ok = (name == "" || fields.Field(name) != null);
}
else if (expr.StartsWith("?"))
{
ok = ValidatePattern(form, expr.Substring(1), pathName, argLimit);
}
else if (expr.StartsWith("["))
{
if (expr.EndsWith("]"))
{
ok = ValidatePattern(form, expr.Substring(1, expr.Length - 2), pathName, argLimit);
}
else
{
ok = false;
}
}
else if (expr.StartsWith("*"))
{
ok = (expr == "*" || expr == "*filled");
}
else if (TryParseFormat(expr, out numeric))
{
ok = numeric <= argLimit - 1;
}
else
{
var formatArgs = expr.Split(':');
var name = formatArgs[0];
if (name == "") name = pathName;
ok = (name == "" || fields.Field(name) != null);
}
if (!ok)
{
break;
}
}
return ok;
}
private string ExpandTemplate(string template, string currentChoice, string noValue, T state, string pathName, object[] args)
{
bool foundUnspecified = false;
@ -126,15 +185,10 @@ namespace Microsoft.Bot.Builder.Form.Advanced
var substitute = "";
if (expr.StartsWith("&"))
{
var spec = expr.Substring(1).Split(':');
var name = spec[0];
if (name == "")
{
// Use default pathname
name = pathName;
}
var name = expr.Substring(1);
if (name == "") name = pathName;
var pathField = _form.Fields.Field(name);
substitute = Normalize(pathField == null ? pathName : pathField.Description(), _annotation.FieldCase);
substitute = Normalize(pathField == null ? pathName : pathField.FieldDescription, _annotation.FieldCase);
}
else if (expr == "||")
{
@ -143,7 +197,7 @@ namespace Microsoft.Bot.Builder.Form.Advanced
var values = _recognizer.ValueDescriptions();
if (_annotation.AllowDefault != BoolDefault.False)
{
if (!field.Optional())
if (!field.Optional)
{
if (!field.IsUnknown(state))
{
@ -218,7 +272,7 @@ namespace Microsoft.Bot.Builder.Form.Advanced
{
builder.Append("\n");
}
foreach (var entry in (from step in _form.Fields where (!filled || !step.IsUnknown(state)) && step.Role() == FieldRole.Value && step.Active(state) select step))
foreach (var entry in (from step in _form.Fields where (!filled || !step.IsUnknown(state)) && step.Role== FieldRole.Value && step.Active(state) select step))
{
builder.Append("* ").AppendLine(format.Prompt(state, entry.Name));
}
@ -328,7 +382,7 @@ namespace Microsoft.Bot.Builder.Form.Advanced
return (foundUnspecified ? null : response.Append(template.Substring(last, template.Length - last)).ToString());
}
private bool TryParseFormat(string format, out int number)
private static bool TryParseFormat(string format, out int number)
{
var args = format.Split(':');
return int.TryParse(args[0], out number);

Просмотреть файл

@ -1,200 +1,207 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license.
//
// Microsoft Bot Framework: http://botframework.com
//
// Bot Builder SDK Github:
// https://github.com/Microsoft/BotBuilder
//
// Copyright (c) Microsoft Corporation
// All rights reserved.
//
// MIT License:
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.Bot.Builder.Form.Advanced
{
/// <summary>
/// Enumeration of special kinds of matches.
/// </summary>
public enum SpecialValues {
/// <summary>
/// Match corresponds to a field, not a specific value in the field.
/// </summary>
Field
};
/// <summary>
/// Describe a possible match in the user input.
/// </summary>
public class TermMatch
{
/// <summary>
/// Construct a match.
/// </summary>
/// <param name="start">Start of match in input string.</param>
/// <param name="length">Length of match in input string.</param>
/// <param name="confidence">Confidence of match, 0-1.0.</param>
/// <param name="value">The underlying C# value for the match.</param>
public TermMatch(int start, int length, double confidence, object value)
{
Start = start;
Length = length;
Confidence = confidence;
Value = value;
}
/// <summary>
/// Start of match in input string.
/// </summary>
public readonly int Start;
/// <summary>
/// End of match in input string.
/// </summary>
public int End { get { return Start + Length; } }
/// <summary>
/// Length of match in input string.
/// </summary>
public readonly int Length;
/// <summary>
/// Confidence of match, 0-1.0.
/// </summary>
public readonly double Confidence;
/// <summary>
/// Underlying C# value.
/// </summary>
public readonly object Value;
/// <summary>
/// Check to see if this covers the same span as match.
/// </summary>
/// <param name="match">TermMatch to compare.</param>
/// <returns>True if both cover the same span.</returns>
public bool Same(TermMatch match)
{
return Start == match.Start && End == match.End;
}
/// <summary>
/// Check to see if this completely covers match.
/// </summary>
/// <param name="match">TermMatch to compare.</param>
/// <returns>True if this covers all of match.</returns>
public bool Covers(TermMatch match)
{
return Start <= match.Start && End >= match.End && (Start != match.Start || End != match.End);
}
/// <summary>
/// Check to see if this overlaps with match in input.
/// </summary>
/// <param name="match">TermMatch to compare.</param>
/// <returns>True if the matches overlap in the input.</returns>
public bool Overlaps(TermMatch match)
{
return (match.Start <= End && Start <= match.Start && End <= match.End) // tmtm
|| (Start <= match.End && match.Start <= Start && match.End <= End) // mtmt
|| (Start <= match.Start && End >= match.End) // tmmt
|| (match.Start <= Start && match.End >= End) // mttm
;
}
public override string ToString()
{
return string.Format("TermMatch({0}, {1}, {2}-{3})", Value, Confidence, Start, Start + Length);
}
public override bool Equals(object obj)
{
return obj is TermMatch && this == (TermMatch)obj;
}
public static bool operator ==(TermMatch m1, TermMatch m2)
{
return ReferenceEquals(m1, m2) || (!ReferenceEquals(m1, null) && !ReferenceEquals(m2, null) && m1.Start == m2.Start && m1.Length == m2.Length && m1.Confidence == m2.Confidence && m1.Value == m2.Value);
}
public static bool operator !=(TermMatch m1, TermMatch m2)
{
return !(m1 == m2);
}
public override int GetHashCode()
{
return Start.GetHashCode() ^ Length.GetHashCode() ^ Confidence.GetHashCode() ^ Value.GetHashCode();
}
}
/// <summary>
/// Interface for recognizers that look for matches in user input.
/// </summary>
/// <typeparam name="T">Underlying form state.</typeparam>
public interface IRecognize<T>
{
/// <summary>
/// Return all possible values or null if a primitive type.
/// </summary>
/// <returns>All possible values.</returns>
IEnumerable<object> Values();
/// <summary>
/// Return all possible value descriptions in order to support enumeration.
/// </summary>
/// <returns>All possible value descriptions.</returns>
IEnumerable<string> ValueDescriptions();
/// <summary>
/// Return the description of a specific value.
/// </summary>
/// <param name="value">Value to get description of.</param>
/// <returns></returns>
string ValueDescription(object value);
/// <summary>
/// Return valid inputs to describe a particular value.
/// </summary>
/// <param name="value">Value being checked.</param>
/// <returns>Valid inputs for describing value.</returns>
IEnumerable<string> ValidInputs(object value);
/// <summary>
/// Return the help string describing what are valid inputs to the recognizer.
/// </summary>
/// <returns>Help on what the recognizer accepts.</returns>
string Help(T state, object defaultValue = null);
/// <summary>
/// Return the matches found in the input.
/// </summary>
/// <param name="input">The input string being matched.</param>
/// <param name="defaultValue">The default value or null if none.</param>
/// <returns>Match records.</returns>
IEnumerable<TermMatch> Matches(string input, object defaultValue = null);
}
}
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license.
//
// Microsoft Bot Framework: http://botframework.com
//
// Bot Builder SDK Github:
// https://github.com/Microsoft/BotBuilder
//
// Copyright (c) Microsoft Corporation
// All rights reserved.
//
// MIT License:
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.Bot.Builder.Form.Advanced
{
/// <summary>
/// Enumeration of special kinds of matches.
/// </summary>
public enum SpecialValues {
/// <summary>
/// Match corresponds to a field, not a specific value in the field.
/// </summary>
Field
};
/// <summary>
/// Describe a possible match in the user input.
/// </summary>
public class TermMatch
{
/// <summary>
/// Construct a match.
/// </summary>
/// <param name="start">Start of match in input string.</param>
/// <param name="length">Length of match in input string.</param>
/// <param name="confidence">Confidence of match, 0-1.0.</param>
/// <param name="value">The underlying C# value for the match.</param>
public TermMatch(int start, int length, double confidence, object value)
{
Start = start;
Length = length;
Confidence = confidence;
Value = value;
}
/// <summary>
/// Start of match in input string.
/// </summary>
public readonly int Start;
/// <summary>
/// End of match in input string.
/// </summary>
public int End { get { return Start + Length; } }
/// <summary>
/// Length of match in input string.
/// </summary>
public readonly int Length;
/// <summary>
/// Confidence of match, 0-1.0.
/// </summary>
public readonly double Confidence;
/// <summary>
/// Underlying C# value.
/// </summary>
public readonly object Value;
/// <summary>
/// Check to see if this covers the same span as match.
/// </summary>
/// <param name="match">TermMatch to compare.</param>
/// <returns>True if both cover the same span.</returns>
public bool Same(TermMatch match)
{
return Start == match.Start && End == match.End;
}
/// <summary>
/// Check to see if this completely covers match.
/// </summary>
/// <param name="match">TermMatch to compare.</param>
/// <returns>True if this covers all of match.</returns>
public bool Covers(TermMatch match)
{
return Start <= match.Start && End >= match.End && (Start != match.Start || End != match.End);
}
/// <summary>
/// Check to see if this overlaps with match in input.
/// </summary>
/// <param name="match">TermMatch to compare.</param>
/// <returns>True if the matches overlap in the input.</returns>
public bool Overlaps(TermMatch match)
{
return (match.Start <= End && Start <= match.Start && End <= match.End) // tmtm
|| (Start <= match.End && match.Start <= Start && match.End <= End) // mtmt
|| (Start <= match.Start && End >= match.End) // tmmt
|| (match.Start <= Start && match.End >= End) // mttm
;
}
public override string ToString()
{
return string.Format("TermMatch({0}, {1}, {2}-{3})", Value, Confidence, Start, Start + Length);
}
public override bool Equals(object obj)
{
return obj is TermMatch && this == (TermMatch)obj;
}
public static bool operator ==(TermMatch m1, TermMatch m2)
{
return ReferenceEquals(m1, m2) || (!ReferenceEquals(m1, null) && !ReferenceEquals(m2, null) && m1.Start == m2.Start && m1.Length == m2.Length && m1.Confidence == m2.Confidence && m1.Value == m2.Value);
}
public static bool operator !=(TermMatch m1, TermMatch m2)
{
return !(m1 == m2);
}
public override int GetHashCode()
{
return Start.GetHashCode() ^ Length.GetHashCode() ^ Confidence.GetHashCode() ^ Value.GetHashCode();
}
}
/// <summary>
/// Interface for recognizers that look for matches in user input.
/// </summary>
/// <typeparam name="T">Underlying form state.</typeparam>
public interface IRecognize<T>
{
#region Documentation
/// <summary> Return the arguments to pass to the prompt. </summary>
///<remarks>For example a numeric recognizer might pass min and max values.</remarks>
/// <returns> An array of arguments.</returns>
#endregion
object[] PromptArgs();
/// <summary>
/// Return all possible values or null if a primitive type.
/// </summary>
/// <returns>All possible values.</returns>
IEnumerable<object> Values();
/// <summary>
/// Return all possible value descriptions in order to support enumeration.
/// </summary>
/// <returns>All possible value descriptions.</returns>
IEnumerable<string> ValueDescriptions();
/// <summary>
/// Return the description of a specific value.
/// </summary>
/// <param name="value">Value to get description of.</param>
/// <returns></returns>
string ValueDescription(object value);
/// <summary>
/// Return valid inputs to describe a particular value.
/// </summary>
/// <param name="value">Value being checked.</param>
/// <returns>Valid inputs for describing value.</returns>
IEnumerable<string> ValidInputs(object value);
/// <summary>
/// Return the help string describing what are valid inputs to the recognizer.
/// </summary>
/// <returns>Help on what the recognizer accepts.</returns>
string Help(T state, object defaultValue = null);
/// <summary>
/// Return the matches found in the input.
/// </summary>
/// <param name="input">The input string being matched.</param>
/// <param name="defaultValue">The default value or null if none.</param>
/// <returns>Match records.</returns>
IEnumerable<TermMatch> Matches(string input, object defaultValue = null);
}
}

Просмотреть файл

@ -1,83 +1,85 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license.
//
// Microsoft Bot Framework: http://botframework.com
//
// Bot Builder SDK Github:
// https://github.com/Microsoft/BotBuilder
//
// Copyright (c) Microsoft Corporation
// All rights reserved.
//
// MIT License:
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Form.Advanced;
namespace Microsoft.Bot.Builder.Form
{
internal enum StepPhase { Ready, Responding, Completed };
internal enum StepType { Field, Confirm, Navigation, Message };
internal struct StepResult
{
internal StepResult(NextStep next, string feedback, string prompt)
{
this.Next = next;
this.Feedback = feedback;
this.Prompt = prompt;
}
internal NextStep Next { get; set; }
internal string Feedback { get; set; }
internal string Prompt { get; set; }
}
internal interface IStep<T>
{
string Name { get; }
StepType Type { get; }
IField<T> Field { get; }
bool Active(T state);
string Start(IDialogContext context, T state, FormState form);
IEnumerable<TermMatch> Match(IDialogContext context, T state, FormState form, string input, out string lastInput);
Task<StepResult> ProcessAsync(IDialogContext context, T state, FormState form, string input, IEnumerable<TermMatch> matches);
string NotUnderstood(IDialogContext context, T state, FormState form, string input);
string Help(T state, FormState form, string commandHelp);
bool Back(IDialogContext context, T state, FormState form);
IEnumerable<string> Dependencies();
}
}
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license.
//
// Microsoft Bot Framework: http://botframework.com
//
// Bot Builder SDK Github:
// https://github.com/Microsoft/BotBuilder
//
// Copyright (c) Microsoft Corporation
// All rights reserved.
//
// MIT License:
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Form.Advanced;
namespace Microsoft.Bot.Builder.Form
{
internal enum StepPhase { Ready, Responding, Completed };
internal enum StepType { Field, Confirm, Navigation, Message };
internal struct StepResult
{
internal StepResult(NextStep next, string feedback, string prompt)
{
this.Next = next;
this.Feedback = feedback;
this.Prompt = prompt;
}
internal NextStep Next { get; set; }
internal string Feedback { get; set; }
internal string Prompt { get; set; }
}
internal interface IStep<T>
{
string Name { get; }
StepType Type { get; }
TemplateBase Annotation { get; }
IField<T> Field { get; }
bool Active(T state);
string Start(IDialogContext context, T state, FormState form);
IEnumerable<TermMatch> Match(IDialogContext context, T state, FormState form, string input, out string lastInput);
Task<StepResult> ProcessAsync(IDialogContext context, T state, FormState form, string input, IEnumerable<TermMatch> matches);
string NotUnderstood(IDialogContext context, T state, FormState form, string input);
string Help(T state, FormState form, string commandHelp);
bool Back(IDialogContext context, T state, FormState form);
IEnumerable<string> Dependencies { get; }
}
}

Просмотреть файл

@ -69,21 +69,25 @@ namespace Microsoft.Bot.Builder.Form.Advanced
{
var configuration = field.Form.Configuration;
_form = field.Form;
_allowNumbers = field.AllowNumbers();
_description = field.Description();
_terms = field.Terms().ToArray();
_values = field.Values().ToArray();
_valueDescriptions = field.ValueDescriptions().ToArray();
_allowNumbers = field.AllowNumbers;
_description = field.FieldDescription;
_terms = field.FieldTerms.ToArray();
_values = field.Values.ToArray();
_valueDescriptions = field.ValueDescriptions.ToArray();
_descriptionDelegate = (value) => field.ValueDescription(value);
_termsDelegate = (value) => field.Terms(value);
_helpFormat = field.Template(field.AllowNumbers()
? (field.AllowsMultiple() ? TemplateUsage.EnumManyNumberHelp : TemplateUsage.EnumOneNumberHelp)
: (field.AllowsMultiple() ? TemplateUsage.EnumManyWordHelp : TemplateUsage.EnumOneWordHelp));
_noPreference = field.Optional() ? configuration.NoPreference.ToArray() : null;
_helpFormat = field.Template(field.AllowNumbers ? (field.AllowsMultiple? TemplateUsage.EnumManyNumberHelp : TemplateUsage.EnumOneNumberHelp)
: (field.AllowsMultiple? TemplateUsage.EnumManyWordHelp : TemplateUsage.EnumOneWordHelp));
_noPreference = field.Optional? configuration.NoPreference.ToArray() : null;
_currentChoice = configuration.CurrentChoice.FirstOrDefault();
BuildPerValueMatcher(configuration.CurrentChoice);
}
public object[] PromptArgs()
{
return new object[0];
}
public IEnumerable<object> Values()
{
return _values;
@ -356,9 +360,9 @@ namespace Microsoft.Bot.Builder.Form.Advanced
_field = field;
_currentChoices = new HashSet<string>(from choice in field.Form.Configuration.CurrentChoice
select choice.Trim().ToLower());
if (field.Optional())
if (field.Optional)
{
if (field.IsNullable())
if (field.IsNullable)
{
_noPreference = new HashSet<string>(from choice in field.Form.Configuration.NoPreference
select choice.Trim().ToLower());
@ -370,6 +374,11 @@ namespace Microsoft.Bot.Builder.Form.Advanced
}
}
public virtual object[] PromptArgs()
{
return new object[0];
}
/// <summary>
/// Abstract method for parsing input.
/// </summary>
@ -377,12 +386,6 @@ namespace Microsoft.Bot.Builder.Form.Advanced
/// <returns>TermMatch if input is a match.</returns>
public abstract TermMatch Parse(string input);
/// <summary>
/// Match input with optional default value.
/// </summary>
/// <param name="input"></param>
/// <param name="defaultValue"></param>
/// <returns></returns>
public virtual IEnumerable<TermMatch> Matches(string input, object defaultValue = null)
{
var matchValue = input.Trim().ToLower();
@ -428,10 +431,10 @@ namespace Microsoft.Bot.Builder.Form.Advanced
protected List<object> HelpArgs(T state, object defaultValue)
{
var args = new List<object>();
if (defaultValue != null || _field.Optional())
if (defaultValue != null || _field.Optional)
{
args.Add(_field.Form.Configuration.CurrentChoice.First() + " or 'c'");
if (_field.Optional())
if (_field.Optional)
{
args.Add(_field.Form.Configuration.NoPreference.First());
}
@ -584,6 +587,11 @@ namespace Microsoft.Bot.Builder.Form.Advanced
_max = (long)max;
}
public override object[] PromptArgs()
{
return _showLimits ? new object[] { _min, _max } : new object[] { null, null };
}
public override string ValueDescription(object value)
{
return ((long)Convert.ChangeType(value, typeof(long))).ToString(_culture.NumberFormat);
@ -646,6 +654,11 @@ namespace Microsoft.Bot.Builder.Form.Advanced
_showLimits = field.Limits(out _min, out _max);
}
public override object[] PromptArgs()
{
return _showLimits ? new object[] { _min, _max } : new object[] { null, null };
}
public override string ValueDescription(object value)
{
return ((double)Convert.ChangeType(value, typeof(double))).ToString(_culture.NumberFormat);

Просмотреть файл

@ -65,6 +65,11 @@ namespace Microsoft.Bot.Builder.Form
}
}
public TemplateBase Annotation
{
get { return _field.Prompt().Annotation; }
}
public IField<T> Field
{
get
@ -82,7 +87,7 @@ namespace Microsoft.Bot.Builder.Form
{
form.SetPhase(StepPhase.Responding);
form.StepState = new FieldStepState(FieldStepStates.SentPrompt);
return _field.Prompt().Prompt(state, _name);
return _field.Prompt().Prompt(state, _name, _field.Prompt().Recognizer().PromptArgs());
}
public IEnumerable<TermMatch> Match(IDialogContext context, T state, FormState form, string input, out string lastInput)
@ -142,7 +147,7 @@ namespace Microsoft.Bot.Builder.Form
// 2) Could be overlapping matches like "onion".
// 3) Could be multiple matches where only one is expected.
if (!_field.AllowsMultiple())
if (!_field.AllowsMultiple)
{
// Create a single group of all possibilities if only want one value
var mergedGroup = groups.SelectMany((group) => group).ToList();
@ -188,7 +193,7 @@ namespace Microsoft.Bot.Builder.Form
}
else
{
if (_field.AllowsMultiple())
if (_field.AllowsMultiple)
{
response = settled;
feedback = await SetValueAsync(state, response, form);
@ -205,11 +210,11 @@ namespace Microsoft.Bot.Builder.Form
var unmatchedWords = string.Join(" ", unmatched);
var nonNoise = Language.NonNoiseWords(Language.WordBreak(unmatchedWords)).ToArray();
fieldState.Unmatched = null;
if (_field.Prompt().Annotation().Feedback == FeedbackOptions.Always)
if (_field.Prompt().Annotation.Feedback == FeedbackOptions.Always)
{
fieldState.Unmatched = string.Join(" ", nonNoise);
}
else if (_field.Prompt().Annotation().Feedback == FeedbackOptions.Auto
else if (_field.Prompt().Annotation.Feedback == FeedbackOptions.Auto
&& nonNoise.Length > 0
&& unmatched.Count() > 0)
{
@ -234,7 +239,7 @@ namespace Microsoft.Bot.Builder.Form
else
{
// No clarification left, so set the field
if (_field.AllowsMultiple())
if (_field.AllowsMultiple)
{
response = fieldState.Settled;
feedback = await SetValueAsync(state, response, form);
@ -291,7 +296,7 @@ namespace Microsoft.Bot.Builder.Form
if (fieldState.State == FieldStepStates.SentClarify)
{
var desc = _field.Form.Fields.Field(_name);
if (desc.AllowsMultiple())
if (desc.AllowsMultiple)
{
desc.SetValue(state, fieldState.Settled);
}
@ -322,9 +327,12 @@ namespace Microsoft.Bot.Builder.Form
return "* " + template.Prompt(state, _name, "* " + template.Recognizer().Help(state, _field.GetValue(state)), commandHelp);
}
public IEnumerable<string> Dependencies()
public IEnumerable<string> Dependencies
{
return new string[0];
get
{
return new string[0];
}
}
private IPrompt<T> Template(TemplateUsage usage, IRecognize<T> recognizer = null)
@ -340,7 +348,7 @@ namespace Microsoft.Bot.Builder.Form
{
desc.SetUnknown(state);
}
else if (desc.AllowsMultiple())
else if (desc.AllowsMultiple)
{
if (value is System.Collections.IEnumerable)
{
@ -388,9 +396,9 @@ namespace Microsoft.Bot.Builder.Form
var field = new Field<T>("__clarify__", FieldRole.Value, _field.Form);
var template = _field.Template(TemplateUsage.Clarify);
var helpTemplate = _field.Template(template.AllowNumbers ? TemplateUsage.EnumOneNumberHelp : TemplateUsage.EnumManyNumberHelp);
field.Prompt(new Prompt(template));
field.Template(_field.Template(TemplateUsage.Clarify));
field.Template(helpTemplate);
field.SetPrompt(new Prompt(template));
field.ReplaceTemplate(_field.Template(TemplateUsage.Clarify));
field.ReplaceTemplate(helpTemplate);
foreach (var value in clarify.Values)
{
field.AddDescription(value, recognizer.ValueDescription(value));
@ -472,6 +480,11 @@ namespace Microsoft.Bot.Builder.Form
}
}
public TemplateBase Annotation
{
get { return _field.Prompt().Annotation; }
}
public string NotUnderstood(IDialogContext context, T state, FormState form, string input)
{
var template = _field.Template(TemplateUsage.NotUnderstood);
@ -509,9 +522,12 @@ namespace Microsoft.Bot.Builder.Form
}
}
public IEnumerable<string> Dependencies()
public IEnumerable<string> Dependencies
{
return _field.Dependencies();
get
{
return _field.Dependencies;
}
}
private readonly IField<T> _field;
@ -527,14 +543,14 @@ namespace Microsoft.Bot.Builder.Form
var field = _fields.Field(_name);
var template = field.Template(TemplateUsage.Navigation);
var recField = new Field<T>("__navigate__", FieldRole.Value, form)
.Prompt(new Prompt(template));
.SetPrompt(new Prompt(template));
var fieldPrompt = field.Template(TemplateUsage.NavigationFormat);
foreach (var value in formState.Next.Names)
{
var prompter = new Prompter<T>(fieldPrompt, form, _fields.Field(value as string).Prompt().Recognizer());
recField
.AddDescription(value, prompter.Prompt(state, value as string))
.AddTerms(value, _fields.Field(value as string).Terms());
.AddTerms(value, _fields.Field(value as string).FieldTerms);
}
var recognizer = new RecognizeEnumeration<T>(recField);
_prompt = new Prompter<T>(template, form, recognizer);
@ -573,6 +589,14 @@ namespace Microsoft.Bot.Builder.Form
}
}
public TemplateBase Annotation
{
get
{
return _prompt.Annotation;
}
}
public string NotUnderstood(IDialogContext context, T state, FormState form, string input)
{
var field = _fields.Field(_name);
@ -607,9 +631,12 @@ namespace Microsoft.Bot.Builder.Form
return "* " + prompt.Prompt(state, _name, "* " + recognizer.Help(state, null), commandHelp);
}
public IEnumerable<string> Dependencies()
public IEnumerable<string> Dependencies
{
return new string[0];
get
{
return new string[0];
}
}
private string _name;
@ -642,9 +669,12 @@ namespace Microsoft.Bot.Builder.Form
return null;
}
public IEnumerable<string> Dependencies()
public IEnumerable<string> Dependencies
{
throw new NotImplementedException();
get
{
throw new NotImplementedException();
}
}
public IField<T> Field
@ -668,6 +698,11 @@ namespace Microsoft.Bot.Builder.Form
}
}
public TemplateBase Annotation
{
get { return _prompt.Annotation; }
}
public string NotUnderstood(IDialogContext context, T state, FormState form, string input)
{
throw new NotImplementedException();

Просмотреть файл

@ -32,6 +32,7 @@
//
using System;
using System.Diagnostics;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Connector;
using Microsoft.Bot.Builder.Form;
@ -140,8 +141,56 @@ namespace Microsoft.Bot.Builder.FormTest
context.Call<T>(form, root.CallChild);
}
public static void TestValidate()
{
try
{
var form = new FormBuilder<PizzaOrder>()
.Message("{NotField}")
.Build();
Debug.Fail("Validation failed");
}
catch (ArgumentException )
{
}
try
{
var form = new FormBuilder<PizzaOrder>()
.Message("[{NotField}]")
.Build();
Debug.Fail("Validation failed");
}
catch (ArgumentException )
{
}
try
{
var form = new FormBuilder<PizzaOrder>()
.Message("{? {[{NotField}]}")
.Build();
Debug.Fail("Validation failed");
}
catch (ArgumentException )
{
}
/*
try
{
var form = new FormBuilder<PizzaOrder>();
.Field(new FieldReflector<PizzaOrder>(nameof(PizzaOrder.Size))
.ReplaceTemplate(new Template(TemplateUsage.Double, "{Notfield}"))
.Build();
Debug.Fail("Validation failed");
}
catch (ArgumentException exception)
{
}
*/
}
static void Main(string[] args)
{
// TestValidate();
var choiceForm = FormDialog.FromType<Choices>();
var callDebug = new CallDialog<Choices>(choiceForm, async (root, context, result) =>
{
@ -168,12 +217,12 @@ namespace Microsoft.Bot.Builder.FormTest
Call(context, root, () => MakeForm(noNumbers: true));
return;
}
case DebugOptions.NoAnnotations:
case DebugOptions.NoAnnotations:
{
Call(context, root, () => MakeForm(noNumbers: true, ignoreAnnotations: true));
return;
}
case DebugOptions.NoFieldOrder:
case DebugOptions.NoFieldOrder:
{
Call(context, root, () => new FormBuilder<PizzaOrder>().Build());
return;