diff --git a/src/DotNetLibraryBase/base.py b/src/DotNetLibraryBase/base.py index 776799f..e281cbc 100644 --- a/src/DotNetLibraryBase/base.py +++ b/src/DotNetLibraryBase/base.py @@ -263,6 +263,17 @@ def get_keyword_types(self, name: str) -> Optional[Mapping[str, Any]]: return {i.Name: [self._convert_type(t) for t in i.Types] for i in keyword_info.Arguments} + def get_keyword_documentation(self, name: str) -> Optional[str]: + """Get documentation for the given keyword.""" + if self._library_info is None: + return None + try: + keyword_info = self._library_info.Keywords[name] + doc = keyword_info.Documentation + return str(doc) if doc is not None else None + except Exception: + return None + def run_keyword(self, name: str, args: Sequence[Any], kwargs: Mapping[str, Any]) -> Any: method = getattr(self._instance, name) real_args = list(args) diff --git a/src/RobotFramework.DotNetLibraryBase/LibraryInfo.cs b/src/RobotFramework.DotNetLibraryBase/LibraryInfo.cs index 5c00bf3..3566138 100644 --- a/src/RobotFramework.DotNetLibraryBase/LibraryInfo.cs +++ b/src/RobotFramework.DotNetLibraryBase/LibraryInfo.cs @@ -8,6 +8,9 @@ namespace RobotFramework.DotNetLibraryBase; using System.Collections; using System.Collections.Generic; using System.Reflection; +using System.Linq; +using System.Xml; +#pragma warning disable 1591 internal enum RobotLibraryConstants { @@ -44,7 +47,19 @@ public KeywordInfo(IEnumerable methods) if (Methods.Length == 0) throw new ArgumentException("At least one method is required", nameof(methods)); - _documentation = new Lazy(() => null); // TODO: Get doc from attribute + _documentation = new Lazy(() => { + // Get XML documentation first + var xmlDoc = Methods[0].GetXmlDocumentation(); + + // Only if XML doc is not available, try attribute + if (string.IsNullOrEmpty(xmlDoc)) + { + var docAttribute = Methods[0].GetCustomAttribute(); + return docAttribute?.Documentation; + } + + return xmlDoc; + }); _tags = new Lazy(() => Array.Empty()); // TODO: Get tags from attribute _arguments = new Lazy(CollectArguments); } diff --git a/src/RobotFramework.DotNetLibraryBase/ReflectionExtensions.cs b/src/RobotFramework.DotNetLibraryBase/ReflectionExtensions.cs new file mode 100644 index 0000000..d3bb9f0 --- /dev/null +++ b/src/RobotFramework.DotNetLibraryBase/ReflectionExtensions.cs @@ -0,0 +1,111 @@ +// SPDX-FileCopyrightText: 2024 Daniel Biehl +// +// SPDX-License-Identifier: Apache-2.0 + +namespace RobotFramework.DotNetLibraryBase; + +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Xml.Linq; +using System.Text; + +public static class ReflectionExtensions +{ + public static string? GetXmlDocumentation(this MethodInfo methodInfo) + { + try + { + // Get the assembly that contains the method + var assembly = methodInfo.DeclaringType?.Assembly; + if (assembly == null) return null; + + // Get the XML documentation file path + var xmlPath = Path.ChangeExtension(assembly.Location, "xml"); + if (!File.Exists(xmlPath)) return null; + + // Load and parse the XML documentation + var doc = XDocument.Load(xmlPath); + + // Try to find the member with exact match first + var member = TryFindMember(doc, methodInfo, exact: true); + + // If not found, try a more lenient search + member ??= TryFindMember(doc, methodInfo, exact: false); + + if (member == null) return null; + + // Return just the summary content + return member.Element("summary")?.Value.Trim(); + } + catch + { + // If anything goes wrong, return null + return null; + } + } + + private static XElement? TryFindMember(XDocument doc, MethodInfo methodInfo, bool exact) + { + var typeName = methodInfo.DeclaringType?.FullName ?? string.Empty; + var methodName = methodInfo.Name; + + // Get all member elements + var members = doc.Root?.Elements("members") + .Elements("member") + .Where(m => m.Attribute("name")?.Value.StartsWith($"M:{typeName}.{methodName}") == true) + .ToList(); + + if (members == null || !members.Any()) + return null; + + if (exact) + { + // Try to find exact match with parameters + var parameters = methodInfo.GetParameters(); + var parameterTypes = parameters.Length == 0 + ? string.Empty + : $"({string.Join(",", parameters.Select(GetTypeNameForXmlDoc))})"; + + var exactName = $"M:{typeName}.{methodName}{parameterTypes}"; + return members.FirstOrDefault(m => + string.Equals(m.Attribute("name")?.Value, exactName, StringComparison.Ordinal)); + } + + // Return first match (most specific one) + return members.FirstOrDefault(); + } + + private static string GetTypeNameForXmlDoc(ParameterInfo parameter) + { + return GetTypeNameForXmlDoc(parameter.ParameterType); + } + + private static string GetTypeNameForXmlDoc(Type type) + { + // Handle array types + if (type.IsArray) + { + return $"{GetTypeNameForXmlDoc(type.GetElementType()!)}[]"; + } + + // Handle generic types + if (type.IsGenericType) + { + var name = type.Name.Split('`')[0]; + var args = string.Join(",", type.GetGenericArguments().Select(t => GetTypeNameForXmlDoc(t))); + return $"{name}{{{args}}}"; + } + + // Handle nullable value types + var underlyingType = Nullable.GetUnderlyingType(type); + if (underlyingType != null) + { + return $"{GetTypeNameForXmlDoc(underlyingType)}?"; + } + + // Default case - use full name + return type.FullName ?? type.Name; + } +} \ No newline at end of file diff --git a/src/RobotFramework.DotNetLibraryBase/RobotFramework.DotNetLibraryBase.csproj b/src/RobotFramework.DotNetLibraryBase/RobotFramework.DotNetLibraryBase.csproj index c95276c..0a12ed8 100644 --- a/src/RobotFramework.DotNetLibraryBase/RobotFramework.DotNetLibraryBase.csproj +++ b/src/RobotFramework.DotNetLibraryBase/RobotFramework.DotNetLibraryBase.csproj @@ -5,6 +5,7 @@ enable enable 10 + true diff --git a/src/RobotFramework.DotNetLibraryBase/RobotKeywordAttributes.cs b/src/RobotFramework.DotNetLibraryBase/RobotKeywordAttributes.cs new file mode 100644 index 0000000..43b33e1 --- /dev/null +++ b/src/RobotFramework.DotNetLibraryBase/RobotKeywordAttributes.cs @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2024 Daniel Biehl +// +// SPDX-License-Identifier: Apache-2.0 + +namespace RobotFramework.DotNetLibraryBase; + +using System; + +[AttributeUsage(AttributeTargets.Method)] +#pragma warning disable 1591 +public class RobotKeywordDocumentationAttribute : Attribute +{ + public string Documentation { get; } + + public RobotKeywordDocumentationAttribute(string documentation) + { + Documentation = documentation; + } +} \ No newline at end of file diff --git a/tests/DotNetDemoLibrary/DocumentationExample.cs b/tests/DotNetDemoLibrary/DocumentationExample.cs new file mode 100644 index 0000000..b956d08 --- /dev/null +++ b/tests/DotNetDemoLibrary/DocumentationExample.cs @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: 2024 Daniel Biehl +// +// SPDX-License-Identifier: Apache-2.0 + +namespace DotNetDemoLibrary; + +using System; + +/// +/// Provides examples of using this class to see the docu +/// +public class DocumentationExample +{ + /// + /// This is an example of XML documentation. + /// The documentation will be shown in Robot Framework. + /// + /// + /// This keyword demonstrates XML documentation in IntelliSense. + /// + public void XmlDocumentedKeyword() + { + Console.WriteLine("This keyword uses XML documentation"); + } + + /// + /// Example with parameters + /// + /// The text to display + /// Number of times to repeat + public void AttributeDocumentedKeyword(string text, int count = 1) + { + for (int i = 0; i < count; i++) + { + Console.WriteLine(text); + } + } + + /// + /// This keyword has both XML documentation... + /// + /// Optional message to display + /// The message that was displayed + public string BothDocumentationTypesKeyword(string message = "Default message") + { + Console.WriteLine(message); + return message; + } + + /// + /// No Docu keyword + /// + /// + /// + /// + public int adding(int a, int b) + { + return a + b; + } +} diff --git a/tests/DotNetDemoLibrary/DotNetDemoLibrary.csproj b/tests/DotNetDemoLibrary/DotNetDemoLibrary.csproj index 8a7681a..0950eed 100644 --- a/tests/DotNetDemoLibrary/DotNetDemoLibrary.csproj +++ b/tests/DotNetDemoLibrary/DotNetDemoLibrary.csproj @@ -5,6 +5,11 @@ enable enable 0.0.1 + true + + + + \ No newline at end of file diff --git a/tests/docu_test.robot b/tests/docu_test.robot new file mode 100644 index 0000000..269b3da --- /dev/null +++ b/tests/docu_test.robot @@ -0,0 +1,12 @@ +*** Settings *** +Library DotNetLibraryBase DotNetDemoLibrary.DocumentationExample, DotNetDemoLibrary + +*** Test Cases *** +Test Documentation Types + XML Documented Keyword + Attribute Documented Keyword Hello from Robot count=3 + ${message}= Both Documentation Types Keyword Custom message + Log ${message} + ${result}= Adding 5 5 + Log ${result} + \ No newline at end of file