diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 8641561f54..be4ec0c376 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -109,3 +109,10 @@ updates:
open-pull-requests-limit: 10
labels:
- TypeScript
+- package-ecosystem: nuget
+ directory: "/cli/commons"
+ schedule:
+ interval: daily
+ open-pull-requests-limit: 10
+ labels:
+ - CLI
diff --git a/.github/workflows/cli-commons.yml b/.github/workflows/cli-commons.yml
new file mode 100644
index 0000000000..99f5269d08
--- /dev/null
+++ b/.github/workflows/cli-commons.yml
@@ -0,0 +1,64 @@
+name: CLI commons
+
+on:
+ workflow_dispatch:
+ push:
+ branches: [ main ]
+ paths: ['cli/commons/**', '.github/workflows/**']
+ pull_request:
+ paths: ['cli/commons/**', '.github/workflows/**']
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ env:
+ relativePath: ./cli/commons
+ solutionName: Microsoft.Kiota.Cli.Commons.sln
+ steps:
+ - uses: actions/checkout@v2.4.0
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v1.9.0
+ with:
+ dotnet-version: 6.0.x
+ - name: Restore dependencies
+ run: dotnet restore ${{ env.solutionName }}
+ working-directory: ${{ env.relativePath }}
+ - name: Build
+ run: dotnet build ${{ env.solutionName }} --no-restore -c Release
+ working-directory: ${{ env.relativePath }}
+ - name: Test
+ run: dotnet test ${{ env.solutionName }} --no-build --verbosity normal -c Release /p:CollectCoverage=true /p:CoverletOutput=TestResults/ /p:CoverletOutputFormat=opencover
+ working-directory: ${{ env.relativePath }}
+ - name: Publish
+ run: dotnet publish ${{ env.solutionName }} --no-restore --no-build --verbosity normal -c Release
+ working-directory: ${{ env.relativePath }}
+ - name: Pack
+ run: dotnet pack ${{ env.solutionName }} --no-restore --no-build --verbosity normal -c Release
+ working-directory: ${{ env.relativePath }}
+ - name: Upload Coverage Results
+ uses: actions/upload-artifact@v2
+ with:
+ name: codeCoverage
+ path: |
+ ${{ env.relativePath }}src/Microsoft.Kiota.Cli.Commons.Tests/TestResults
+ - name: Upload Nuget Package
+ uses: actions/upload-artifact@v2
+ with:
+ name: drop
+ path: |
+ ${{ env.relativePath }}/src/bin/Release/*.nupkg
+ deploy:
+ if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
+ environment:
+ name: staging_feeds
+ runs-on: ubuntu-latest
+ needs: [build]
+ steps:
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v1.9.0
+ with:
+ dotnet-version: 6.0.x
+ - uses: actions/download-artifact@v2
+ with:
+ name: drop
+ - run: dotnet nuget push "*.nupkg" --skip-duplicate -s https://nuget.pkg.github.com/microsoft/index.json -k ${{ secrets.PUBLISH_GH_TOKEN }}
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index fc908e7e9e..927dbd2b35 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -15,10 +15,10 @@ on:
workflow_dispatch:
push:
branches: [ main ]
- path-ignore: ['abstractions/**', 'authentication/**', 'serialization/**', 'http/**', '**.md', '.vscode/**', '**.svg']
+ path-ignore: ['abstractions/**', 'authentication/**', 'serialization/**', 'http/**', 'cli/**', '**.md', '.vscode/**', '**.svg']
pull_request:
# The branches below must be a subset of the branches above
- path-ignore: ['abstractions/**', 'authentication/**', 'serialization/**', 'http/**', '**.md', '.vscode/**', '**.svg']
+ path-ignore: ['abstractions/**', 'authentication/**', 'serialization/**', 'http/**', 'cli/**', '**.md', '.vscode/**', '**.svg']
schedule:
- cron: '20 9 * * 5'
diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml
index e7790b5fb2..5d95adc35b 100644
--- a/.github/workflows/sonarcloud.yml
+++ b/.github/workflows/sonarcloud.yml
@@ -4,10 +4,10 @@ on:
push:
branches:
- main
- path-ignore: ['abstractions/**', 'authentication/**', 'serialization/**', 'http/**', '**.md', '.vscode/**', '**.svg']
+ path-ignore: ['abstractions/**', 'authentication/**', 'serialization/**', 'http/**', 'cli/**', '**.md', '.vscode/**', '**.svg']
pull_request:
types: [opened, synchronize, reopened]
- path-ignore: ['abstractions/**', 'authentication/**', 'serialization/**', 'http/**', '**.md', '.vscode/**', '**.svg']
+ path-ignore: ['abstractions/**', 'authentication/**', 'serialization/**', 'http/**', 'cli/**', '**.md', '.vscode/**', '**.svg']
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
diff --git a/README.md b/README.md
index 10542c884a..c5e5279ee9 100644
--- a/README.md
+++ b/README.md
@@ -28,6 +28,7 @@ The following table provides an overview of the languages supported by Kiota and
| Python | [▶](https://github.com/microsoft/kiota/projects/3) | [✔](./abstractions/python) | ❌ | [Anonymous](./abstractions/python/kiota/abstractions/authentication/anonymous_authentication_provider.py), [Azure](./authentication/python/azure) | ❌ | |
| Ruby | [✔](https://github.com/microsoft/kiota/projects/6) | [✔](./abstractions/ruby) | [JSON](./serialization/ruby/json/microsoft_kiota_serialization) | [Anonymous](./abstractions/ruby/microsoft_kiota_abstractions/lib/microsoft_kiota_abstractions/authentication/anonymous_authentication_provider.rb), [❌ Azure](https://github.com/microsoft/kiota/issues/421) | [✔](./http/ruby/nethttp/microsoft_kiota_nethttplibrary)| [link](https://microsoft.github.io/kiota/get-started/ruby) |
| TypeScript/JavaScript | [✔](https://github.com/microsoft/kiota/projects/2) | [✔](./abstractions/typescript) | [JSON](./serialization/typescript/json) | [Anonymous](./abstractions/typescript/src/authentication/anonymousAuthenticationProvider.ts), [Azure](./authentication/typescript/azure) | [✔](./http/typescript/fetch) | [link](https://microsoft.github.io/kiota/get-started/typescript) |
+| Shell | [✔](https://github.com/microsoft/kiota/projects/10) | [✔](./abstractions/dotnet), [✔](./cli/commonc) | [JSON](./serialization/dotnet/json) | [Anonymous](./abstractions/dotnet/src/authentication/AnonymousAuthenticationProvider.cs), [Azure](./authentication/dotnet/azure) | [✔](./http/dotnet/httpclient) | [link](https://microsoft.github.io/kiota/get-started/dotnet) |
> Legend: ✔ -> in preview, ❌ -> not started, ▶ -> in progress.
diff --git a/cli/commons/Microsoft.Kiota.Cli.Commons.sln b/cli/commons/Microsoft.Kiota.Cli.Commons.sln
new file mode 100644
index 0000000000..1608855473
--- /dev/null
+++ b/cli/commons/Microsoft.Kiota.Cli.Commons.sln
@@ -0,0 +1,34 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.30114.105
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8DEB9AF3-BEA6-4E73-BB5E-EBC1DFE6AF22}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Kiota.Cli.Commons", "src\Microsoft.Kiota.Cli.Commons\Microsoft.Kiota.Cli.Commons.csproj", "{23DD14C5-3060-4498-B2F9-85B68770AE0B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Kiota.Cli.Commons.Tests", "src\Microsoft.Kiota.Cli.Commons.Tests\Microsoft.Kiota.Cli.Commons.Tests.csproj", "{D1228DD9-C98F-46C1-911A-65AE2D34DBE5}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {23DD14C5-3060-4498-B2F9-85B68770AE0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {23DD14C5-3060-4498-B2F9-85B68770AE0B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {23DD14C5-3060-4498-B2F9-85B68770AE0B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {23DD14C5-3060-4498-B2F9-85B68770AE0B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D1228DD9-C98F-46C1-911A-65AE2D34DBE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D1228DD9-C98F-46C1-911A-65AE2D34DBE5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D1228DD9-C98F-46C1-911A-65AE2D34DBE5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D1228DD9-C98F-46C1-911A-65AE2D34DBE5}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {23DD14C5-3060-4498-B2F9-85B68770AE0B} = {8DEB9AF3-BEA6-4E73-BB5E-EBC1DFE6AF22}
+ {D1228DD9-C98F-46C1-911A-65AE2D34DBE5} = {8DEB9AF3-BEA6-4E73-BB5E-EBC1DFE6AF22}
+ EndGlobalSection
+EndGlobal
diff --git a/cli/commons/README.md b/cli/commons/README.md
new file mode 100644
index 0000000000..a2ee63c92a
--- /dev/null
+++ b/cli/commons/README.md
@@ -0,0 +1,3 @@
+# Kiota CLI Commons Package
+
+Contains CLI specific types that are referenced in code generated by the shell language.
\ No newline at end of file
diff --git a/cli/commons/src/Microsoft.Kiota.Cli.Commons.Tests/Microsoft.Kiota.Cli.Commons.Tests.csproj b/cli/commons/src/Microsoft.Kiota.Cli.Commons.Tests/Microsoft.Kiota.Cli.Commons.Tests.csproj
new file mode 100644
index 0000000000..5f88768850
--- /dev/null
+++ b/cli/commons/src/Microsoft.Kiota.Cli.Commons.Tests/Microsoft.Kiota.Cli.Commons.Tests.csproj
@@ -0,0 +1,23 @@
+
+
+
+ net6.0
+ enable
+
+ false
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
diff --git a/cli/commons/src/Microsoft.Kiota.Cli.Commons/IO/FormatterType.cs b/cli/commons/src/Microsoft.Kiota.Cli.Commons/IO/FormatterType.cs
new file mode 100644
index 0000000000..96618b0a82
--- /dev/null
+++ b/cli/commons/src/Microsoft.Kiota.Cli.Commons/IO/FormatterType.cs
@@ -0,0 +1,8 @@
+namespace Microsoft.Kiota.Cli.Commons.IO;
+
+public enum FormatterType
+{
+ JSON,
+ TABLE,
+ NONE
+}
diff --git a/cli/commons/src/Microsoft.Kiota.Cli.Commons/IO/IOutputFormatter.cs b/cli/commons/src/Microsoft.Kiota.Cli.Commons/IO/IOutputFormatter.cs
new file mode 100644
index 0000000000..924b8a754a
--- /dev/null
+++ b/cli/commons/src/Microsoft.Kiota.Cli.Commons/IO/IOutputFormatter.cs
@@ -0,0 +1,10 @@
+using System.CommandLine;
+
+namespace Microsoft.Kiota.Cli.Commons.IO;
+
+public interface IOutputFormatter
+{
+ void WriteOutput(string content, IConsole console);
+
+ void WriteOutput(Stream content, IConsole console);
+}
diff --git a/cli/commons/src/Microsoft.Kiota.Cli.Commons/IO/IOutputFormatterFactory.cs b/cli/commons/src/Microsoft.Kiota.Cli.Commons/IO/IOutputFormatterFactory.cs
new file mode 100644
index 0000000000..4d8026a99a
--- /dev/null
+++ b/cli/commons/src/Microsoft.Kiota.Cli.Commons/IO/IOutputFormatterFactory.cs
@@ -0,0 +1,6 @@
+namespace Microsoft.Kiota.Cli.Commons.IO;
+
+public interface IOutputFormatterFactory
+{
+ IOutputFormatter GetFormatter(FormatterType formatterType);
+}
diff --git a/cli/commons/src/Microsoft.Kiota.Cli.Commons/Microsoft.Kiota.Cli.Commons.csproj b/cli/commons/src/Microsoft.Kiota.Cli.Commons/Microsoft.Kiota.Cli.Commons.csproj
new file mode 100644
index 0000000000..062e460d3f
--- /dev/null
+++ b/cli/commons/src/Microsoft.Kiota.Cli.Commons/Microsoft.Kiota.Cli.Commons.csproj
@@ -0,0 +1,14 @@
+
+
+
+ net6.0
+ enable
+ enable
+ 0.1.0
+
+
+
+
+
+
+
diff --git a/src/Kiota.Builder/CodeDOM/CodeMethod.cs b/src/Kiota.Builder/CodeDOM/CodeMethod.cs
index 048b437b30..4cc1faff2e 100644
--- a/src/Kiota.Builder/CodeDOM/CodeMethod.cs
+++ b/src/Kiota.Builder/CodeDOM/CodeMethod.cs
@@ -20,6 +20,7 @@ public enum CodeMethodKind
RequestBuilderWithParameters,
RawUrlConstructor,
NullCheck,
+ CommandBuilder,
}
public enum HttpMethod {
Get,
@@ -48,6 +49,11 @@ public class CodeMethod : CodeTerminal, ICloneable, IDocumentedElement
public void RemoveParametersByKind(params CodeParameterKind[] kinds) {
parameters.RemoveAll(p => p.IsOfKind(kinds));
}
+
+ public void ClearParameters()
+ {
+ parameters.Clear();
+ }
public IEnumerable Parameters { get => parameters; }
public bool IsStatic {get;set;} = false;
public bool IsAsync {get;set;} = true;
@@ -55,7 +61,28 @@ public void RemoveParametersByKind(params CodeParameterKind[] kinds) {
///
/// The property this method accesses to when it's a getter or setter.
///
- public CodeProperty AccessedProperty { get; set; }
+ public CodeProperty AccessedProperty { get; set;
+ }
+ ///
+ /// The combination of the path and query parameters for the current URL.
+ /// Only use this property if the language you are generating for doesn't support fluent API style (e.g. Shell/CLI)
+ ///
+ public IEnumerable PathAndQueryParameters
+ {
+ get; private set;
+ }
+ public void AddPathOrQueryParameter(params CodeParameter[] parameters)
+ {
+ if (parameters == null || !parameters.Any()) return;
+ foreach (var parameter in parameters)
+ {
+ EnsureElementsAreChildren(parameter);
+ }
+ if (PathAndQueryParameters == null)
+ PathAndQueryParameters = new List(parameters);
+ else if (PathAndQueryParameters is List cast)
+ cast.AddRange(parameters);
+ }
public bool IsOfKind(params CodeMethodKind[] kinds) {
return kinds?.Contains(MethodKind) ?? false;
}
@@ -83,7 +110,15 @@ public bool IsSerializationMethod {
/// The base url for every request read from the servers property on the description.
/// Only provided for constructor on Api client
///
- public string BaseUrl { get; set; }
+ public string BaseUrl { get; set;
+ }
+
+ ///
+ /// This is currently used for CommandBuilder methods to get the original name without the Build prefix & Command suffix.
+ /// Avoids regex operations
+ ///
+ public string SimpleName { get; set; } = String.Empty;
+
///
/// Mapping of the error code and response types for this method.
///
diff --git a/src/Kiota.Builder/Extensions/CodeParametersEnumerableExtensions.cs b/src/Kiota.Builder/Extensions/CodeParametersEnumerableExtensions.cs
index a7904ce4e9..a336c9e48b 100644
--- a/src/Kiota.Builder/Extensions/CodeParametersEnumerableExtensions.cs
+++ b/src/Kiota.Builder/Extensions/CodeParametersEnumerableExtensions.cs
@@ -1,4 +1,4 @@
-using System.Linq;
+using System.Linq;
using System.Collections.Generic;
namespace Kiota.Builder {
diff --git a/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs b/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs
index 929d30beb9..a9376951ab 100644
--- a/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs
+++ b/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs
@@ -120,8 +120,12 @@ private static string SanitizePathParameterNames(string original) {
if(string.IsNullOrEmpty(original) || !original.Contains('{')) return original;
var parameters = pathParamMatcher.Matches(original);
foreach(var value in parameters.Select(x => x.Value))
- original = original.Replace(value, value.Replace('-', '_'));
+ original = original.SanitizePathParameterName();
return original;
}
+ public static string SanitizePathParameterName(this string original) {
+ if(string.IsNullOrEmpty(original)) return original;
+ return original.Replace('-', '_');
+ }
}
}
diff --git a/src/Kiota.Builder/Extensions/StringExtensions.cs b/src/Kiota.Builder/Extensions/StringExtensions.cs
index fd0e2b6cee..99b948ca7a 100644
--- a/src/Kiota.Builder/Extensions/StringExtensions.cs
+++ b/src/Kiota.Builder/Extensions/StringExtensions.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
@@ -26,7 +26,7 @@ public static string ReplaceValueIdentifier(this string original) =>
public static string TrimQuotes(this string original) =>
original?.Trim('\'', '"');
- public static string ToSnakeCase(this string name)
+ public static string ToSnakeCase(this string name, char separator = '_')
{
if(string.IsNullOrEmpty(name)) return name;
var chunks = name.Split('-', StringSplitOptions.RemoveEmptyEntries);
@@ -40,7 +40,7 @@ public static string ToSnakeCase(this string name)
foreach (var item in identifier[1..])
{
if(char.IsUpper(item)) {
- sb.Append('_');
+ sb.Append(separator);
sb.Append(char.ToLowerInvariant(item));
} else {
sb.Append(item);
diff --git a/src/Kiota.Builder/GenerationLanguage.cs b/src/Kiota.Builder/GenerationLanguage.cs
index 22ddcb5397..021f50d2e5 100644
--- a/src/Kiota.Builder/GenerationLanguage.cs
+++ b/src/Kiota.Builder/GenerationLanguage.cs
@@ -1,4 +1,4 @@
-namespace Kiota.Builder {
+namespace Kiota.Builder {
public enum GenerationLanguage {
CSharp,
Java,
@@ -7,5 +7,6 @@ public enum GenerationLanguage {
Python,
Go,
Ruby,
+ Shell
}
}
diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs
index 79a7a73bdc..4a67f82d1b 100644
--- a/src/Kiota.Builder/KiotaBuilder.cs
+++ b/src/Kiota.Builder/KiotaBuilder.cs
@@ -626,10 +626,40 @@ private void CreateOperationMethods(OpenApiUrlTreeNode currentNode, OperationTyp
Description = operation.Description ?? operation.Summary,
ReturnType = new CodeType { Name = "RequestInformation", IsNullable = false, IsExternal = true},
};
+ if (config.Language == GenerationLanguage.Shell)
+ SetPathAndQueryParameters(generatorMethod, currentNode, operation);
parentClass.AddMethod(generatorMethod);
AddRequestBuilderMethodParameters(currentNode, operation, parameterClass, generatorMethod);
logger.LogTrace("Creating method {name} of {type}", generatorMethod.Name, generatorMethod.ReturnType);
}
+ private static void SetPathAndQueryParameters(CodeMethod target, OpenApiUrlTreeNode currentNode, OpenApiOperation operation)
+ {
+ var pathAndQueryParameters = currentNode
+ .PathItems[Constants.DefaultOpenApiLabel]
+ .Parameters
+ .Where(x => x.In == ParameterLocation.Path || x.In == ParameterLocation.Query)
+ .Select(x => new CodeParameter
+ {
+ Name = x.Name.TrimStart('$').SanitizePathParameterName(),
+ Type = GetQueryParameterType(x.Schema),
+ Description = x.Description,
+ ParameterKind = x.In == ParameterLocation.Path ? CodeParameterKind.Path : CodeParameterKind.QueryParameter,
+ Optional = !x.Required
+ })
+ .Union(operation
+ .Parameters
+ .Where(x => x.In == ParameterLocation.Path || x.In == ParameterLocation.Query)
+ .Select(x => new CodeParameter
+ {
+ Name = x.Name.TrimStart('$').SanitizePathParameterName(),
+ Type = GetQueryParameterType(x.Schema),
+ Description = x.Description,
+ ParameterKind = x.In == ParameterLocation.Path ? CodeParameterKind.Path : CodeParameterKind.QueryParameter,
+ Optional = !x.Required
+ }))
+ .ToArray();
+ target.AddPathOrQueryParameter(pathAndQueryParameters);
+ }
private void AddRequestBuilderMethodParameters(OpenApiUrlTreeNode currentNode, OpenApiOperation operation, CodeClass parameterClass, CodeMethod method) {
var nonBinaryRequestBody = operation.RequestBody?.Content?.FirstOrDefault(x => !RequestBodyBinaryContentType.Equals(x.Key, StringComparison.OrdinalIgnoreCase));
if (nonBinaryRequestBody.HasValue && nonBinaryRequestBody.Value.Value != null)
@@ -979,6 +1009,13 @@ private CodeClass CreateOperationParameter(OpenApiUrlTreeNode node, OperationTyp
return parameterClass;
} else return null;
}
+ private static CodeType GetQueryParameterType(OpenApiSchema schema) =>
+ new()
+ {
+ IsExternal = true,
+ Name = schema.Items?.Type ?? schema.Type,
+ CollectionKind = schema.IsArray() ? CodeType.CodeTypeCollectionKind.Array : default,
+ };
private static string FixQueryParameterIdentifier(OpenApiParameter parameter)
{
diff --git a/src/Kiota.Builder/Refiners/CSharpRefiner.cs b/src/Kiota.Builder/Refiners/CSharpRefiner.cs
index 2a73363c08..38707d80ab 100644
--- a/src/Kiota.Builder/Refiners/CSharpRefiner.cs
+++ b/src/Kiota.Builder/Refiners/CSharpRefiner.cs
@@ -39,7 +39,7 @@ public override void Refine(CodeNamespace generatedCode)
"Microsoft.Kiota.Abstractions"
);
}
- private static void DisambiguatePropertiesWithClassNames(CodeElement currentElement) {
+ protected static void DisambiguatePropertiesWithClassNames(CodeElement currentElement) {
if(currentElement is CodeClass currentClass) {
var sameNameProperty = currentClass.Properties
.FirstOrDefault(x => x.Name.Equals(currentClass.Name, StringComparison.OrdinalIgnoreCase));
@@ -52,7 +52,7 @@ private static void DisambiguatePropertiesWithClassNames(CodeElement currentElem
}
CrawlTree(currentElement, DisambiguatePropertiesWithClassNames);
}
- private static void MakeEnumPropertiesNullable(CodeElement currentElement) {
+ protected static void MakeEnumPropertiesNullable(CodeElement currentElement) {
if(currentElement is CodeClass currentClass && currentClass.IsOfKind(CodeClassKind.Model))
currentClass.Properties
.Where(x => x.Type is CodeType propType && propType.TypeDefinition is CodeEnum)
@@ -61,7 +61,7 @@ private static void MakeEnumPropertiesNullable(CodeElement currentElement) {
CrawlTree(currentElement, MakeEnumPropertiesNullable);
}
- private static readonly AdditionalUsingEvaluator[] defaultUsingEvaluators = new AdditionalUsingEvaluator[] {
+ protected static readonly AdditionalUsingEvaluator[] defaultUsingEvaluators = new AdditionalUsingEvaluator[] {
new (x => x is CodeProperty prop && prop.IsOfKind(CodePropertyKind.RequestAdapter),
"Microsoft.Kiota.Abstractions", "IRequestAdapter"),
new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestGenerator),
@@ -94,21 +94,21 @@ private static void MakeEnumPropertiesNullable(CodeElement currentElement) {
new (x => x is CodeProperty prop && prop.IsOfKind(CodePropertyKind.BackingStore),
"Microsoft.Kiota.Abstractions.Store", "IBackingStore", "IBackedModel", "BackingStoreFactorySingleton" ),
};
- private static void CapitalizeNamespacesFirstLetters(CodeElement current) {
+ protected static void CapitalizeNamespacesFirstLetters(CodeElement current) {
if(current is CodeNamespace currentNamespace)
currentNamespace.Name = currentNamespace.Name?.Split('.')?.Select(x => x.ToFirstCharacterUpperCase())?.Aggregate((x, y) => $"{x}.{y}");
CrawlTree(current, CapitalizeNamespacesFirstLetters);
}
- private static void AddAsyncSuffix(CodeElement currentElement) {
+ protected static void AddAsyncSuffix(CodeElement currentElement) {
if(currentElement is CodeMethod currentMethod && currentMethod.IsAsync)
currentMethod.Name += "Async";
CrawlTree(currentElement, AddAsyncSuffix);
}
- private static void CorrectPropertyType(CodeProperty currentProperty)
+ protected static void CorrectPropertyType(CodeProperty currentProperty)
{
CorrectDateTypes(currentProperty.Parent as CodeClass, DateTypesReplacements, currentProperty.Type);
}
- private static void CorrectMethodType(CodeMethod currentMethod)
+ protected static void CorrectMethodType(CodeMethod currentMethod)
{
CorrectDateTypes(currentMethod.Parent as CodeClass, DateTypesReplacements, currentMethod.Parameters
.Select(x => x.Type)
diff --git a/src/Kiota.Builder/Refiners/ILanguageRefiner.cs b/src/Kiota.Builder/Refiners/ILanguageRefiner.cs
index 04a0026878..e97583c905 100644
--- a/src/Kiota.Builder/Refiners/ILanguageRefiner.cs
+++ b/src/Kiota.Builder/Refiners/ILanguageRefiner.cs
@@ -1,4 +1,4 @@
-namespace Kiota.Builder.Refiners
+namespace Kiota.Builder.Refiners
{
public interface ILanguageRefiner
{
@@ -24,6 +24,9 @@ public static void Refine(GenerationConfiguration config, CodeNamespace generate
case GenerationLanguage.Go:
new GoRefiner(config).Refine(generatedCode);
break;
+ case GenerationLanguage.Shell:
+ new ShellRefiner(config).Refine(generatedCode);
+ break;
default:
break; //Do nothing
}
diff --git a/src/Kiota.Builder/Refiners/ShellRefiner.cs b/src/Kiota.Builder/Refiners/ShellRefiner.cs
new file mode 100644
index 0000000000..4da99c323f
--- /dev/null
+++ b/src/Kiota.Builder/Refiners/ShellRefiner.cs
@@ -0,0 +1,160 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Kiota.Builder.Extensions;
+
+namespace Kiota.Builder.Refiners
+{
+ public class ShellRefiner : CSharpRefiner, ILanguageRefiner
+ {
+ public ShellRefiner(GenerationConfiguration configuration) : base(configuration) { }
+ public override void Refine(CodeNamespace generatedCode)
+ {
+ AddDefaultImports(generatedCode, defaultUsingEvaluators);
+ AddDefaultImports(generatedCode, additionalUsingEvaluators);
+ CorrectCoreType(generatedCode, CorrectMethodType, CorrectPropertyType);
+ MoveClassesWithNamespaceNamesUnderNamespace(generatedCode);
+ ConvertUnionTypesToWrapper(generatedCode, _configuration.UsesBackingStore);
+ AddPropertiesAndMethodTypesImports(generatedCode, false, false, false);
+ AddInnerClasses(generatedCode, false);
+ AddParsableInheritanceForModelClasses(generatedCode, "IParsable");
+ CapitalizeNamespacesFirstLetters(generatedCode);
+ ReplaceBinaryByNativeType(generatedCode, "Stream", "System.IO");
+ MakeEnumPropertiesNullable(generatedCode);
+ CreateCommandBuilders(generatedCode);
+ /* Exclude the following as their names will be capitalized making the change unnecessary in this case sensitive language
+ * code classes, class declarations, property names, using declarations, namespace names
+ * Exclude CodeMethod as the return type will also be capitalized (excluding the CodeType is not enough since this is evaluated at the code method level)
+ */
+ ReplaceReservedNames(
+ generatedCode,
+ new CSharpReservedNamesProvider(), x => $"@{x.ToFirstCharacterUpperCase()}",
+ new HashSet { typeof(CodeClass), typeof(CodeClass.Declaration), typeof(CodeProperty), typeof(CodeUsing), typeof(CodeNamespace), typeof(CodeMethod) }
+ );
+ DisambiguatePropertiesWithClassNames(generatedCode);
+ AddConstructorsForDefaultValues(generatedCode, false);
+ AddSerializationModulesImport(generatedCode);
+ }
+
+ private static void CreateCommandBuilders(CodeElement currentElement)
+ {
+ if (currentElement is CodeClass currentClass && currentClass.IsOfKind(CodeClassKind.RequestBuilder))
+ {
+ // Replace Nav Properties with BuildXXXCommand methods
+ var navProperties = currentClass.GetChildElements().OfType().Where(e => e.IsOfKind(CodePropertyKind.RequestBuilder));
+ foreach (var navProp in navProperties)
+ {
+ var method = CreateBuildCommandMethod(navProp, currentClass);
+ currentClass.AddMethod(method);
+ currentClass.RemoveChildElement(navProp);
+ }
+
+ // Build command for indexers
+ var indexers = currentClass.GetChildElements().OfType();
+ var classHasIndexers = indexers.Any();
+ CreateCommandBuildersFromIndexers(currentClass, indexers);
+
+ // Clone executors & convert to build command
+ var requestExecutors = currentClass.GetChildElements().OfType().Where(e => e.IsOfKind(CodeMethodKind.RequestExecutor));
+ CreateCommandBuildersFromRequestExecutors(currentClass, classHasIndexers, requestExecutors);
+
+ // Build root command
+ var clientConstructor = currentClass.GetChildElements().OfType().FirstOrDefault(m => m.IsOfKind(CodeMethodKind.ClientConstructor));
+ if (clientConstructor != null)
+ {
+ var rootMethod = new CodeMethod
+ {
+ Name = "BuildRootCommand",
+ Description = clientConstructor.Description,
+ IsAsync = false,
+ MethodKind = CodeMethodKind.CommandBuilder,
+ ReturnType = new CodeType { Name = "Command", IsExternal = true },
+ OriginalMethod = clientConstructor,
+ };
+ currentClass.AddMethod(rootMethod);
+ }
+ }
+ CrawlTree(currentElement, CreateCommandBuilders);
+ }
+
+ private static void CreateCommandBuildersFromRequestExecutors(CodeClass currentClass, bool classHasIndexers, IEnumerable requestMethods)
+ {
+ foreach (var requestMethod in requestMethods)
+ {
+ CodeMethod clone = requestMethod.Clone() as CodeMethod;
+ var cmdName = clone.HttpMethod switch
+ {
+ HttpMethod.Get when classHasIndexers => "List",
+ HttpMethod.Post when classHasIndexers => "Create",
+ _ => clone.Name,
+ };
+
+ clone.IsAsync = false;
+ clone.Name = $"Build{cmdName}Command";
+ clone.Description = requestMethod.Description;
+ clone.ReturnType = CreateCommandType();
+ clone.MethodKind = CodeMethodKind.CommandBuilder;
+ clone.OriginalMethod = requestMethod;
+ clone.SimpleName = cmdName;
+ clone.ClearParameters();
+ currentClass.AddMethod(clone);
+ currentClass.RemoveChildElement(requestMethod);
+ }
+ }
+
+ private static void CreateCommandBuildersFromIndexers(CodeClass currentClass, IEnumerable indexers)
+ {
+ foreach (var indexer in indexers)
+ {
+ var method = new CodeMethod
+ {
+ Name = "BuildCommand",
+ IsAsync = false,
+ MethodKind = CodeMethodKind.CommandBuilder,
+ OriginalIndexer = indexer
+ };
+
+ // ReturnType setter assigns the parent
+ method.ReturnType = CreateCommandType();
+ method.ReturnType.CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Complex;
+ currentClass.AddMethod(method);
+ currentClass.RemoveChildElement(indexer);
+ }
+ }
+
+ private static CodeType CreateCommandType()
+ {
+ return new CodeType
+ {
+ Name = "Command",
+ IsExternal = true,
+ };
+ }
+
+ private static CodeMethod CreateBuildCommandMethod(CodeProperty navProperty, CodeClass parent)
+ {
+ var codeMethod = new CodeMethod
+ {
+ IsAsync = false,
+ Name = $"Build{navProperty.Name.ToFirstCharacterUpperCase()}Command",
+ MethodKind = CodeMethodKind.CommandBuilder
+ };
+ codeMethod.ReturnType = CreateCommandType();
+ codeMethod.AccessedProperty = navProperty;
+ codeMethod.SimpleName = navProperty.Name;
+ codeMethod.Parent = parent;
+ return codeMethod;
+ }
+
+ private static readonly AdditionalUsingEvaluator[] additionalUsingEvaluators = new AdditionalUsingEvaluator[] {
+ new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.RequestBuilder),
+ "System.CommandLine", "Command", "RootCommand", "IConsole"),
+ new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.RequestBuilder),
+ "Microsoft.Kiota.Cli.Commons.IO", "IOutputFormatter", "IOutputFormatterFactory", "FormatterType"),
+ new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.RequestBuilder),
+ "System.Text", "Encoding"),
+ };
+ }
+}
diff --git a/src/Kiota.Builder/Writers/CSharp/CSharpWriter.cs b/src/Kiota.Builder/Writers/CSharp/CSharpWriter.cs
index d37bc33762..ea5e72aebc 100644
--- a/src/Kiota.Builder/Writers/CSharp/CSharpWriter.cs
+++ b/src/Kiota.Builder/Writers/CSharp/CSharpWriter.cs
@@ -6,13 +6,13 @@ public CSharpWriter(string rootPath, string clientNamespaceName)
{
PathSegmenter = new CSharpPathSegmenter(rootPath, clientNamespaceName);
var conventionService = new CSharpConventionService();
- AddCodeElementWriter(new CodeClassDeclarationWriter(conventionService));
- AddCodeElementWriter(new CodeClassEndWriter(conventionService));
- AddCodeElementWriter(new CodeEnumWriter(conventionService));
- AddCodeElementWriter(new CodeIndexerWriter(conventionService));
- AddCodeElementWriter(new CodeMethodWriter(conventionService));
- AddCodeElementWriter(new CodePropertyWriter(conventionService));
- AddCodeElementWriter(new CodeTypeWriter(conventionService));
+ AddOrReplaceCodeElementWriter(new CodeClassDeclarationWriter(conventionService));
+ AddOrReplaceCodeElementWriter(new CodeClassEndWriter(conventionService));
+ AddOrReplaceCodeElementWriter(new CodeEnumWriter(conventionService));
+ AddOrReplaceCodeElementWriter(new CodeIndexerWriter(conventionService));
+ AddOrReplaceCodeElementWriter(new CodeMethodWriter(conventionService));
+ AddOrReplaceCodeElementWriter(new CodePropertyWriter(conventionService));
+ AddOrReplaceCodeElementWriter(new CodeTypeWriter(conventionService));
}
}
diff --git a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs
index 668c0862dd..a4933014d8 100644
--- a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs
+++ b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs
@@ -6,13 +6,13 @@
namespace Kiota.Builder.Writers.CSharp;
public class CodeMethodWriter : BaseElementWriter
{
- public CodeMethodWriter(CSharpConventionService conventionService): base(conventionService) { }
+ public CodeMethodWriter(CSharpConventionService conventionService) : base(conventionService) { }
public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter writer)
{
- if(codeElement == null) throw new ArgumentNullException(nameof(codeElement));
- if(codeElement.ReturnType == null) throw new InvalidOperationException($"{nameof(codeElement.ReturnType)} should not be null");
- if(writer == null) throw new ArgumentNullException(nameof(writer));
- if(!(codeElement.Parent is CodeClass)) throw new InvalidOperationException("the parent of a method should be a class");
+ if (codeElement == null) throw new ArgumentNullException(nameof(codeElement));
+ if (codeElement.ReturnType == null) throw new InvalidOperationException($"{nameof(codeElement.ReturnType)} should not be null");
+ if (writer == null) throw new ArgumentNullException(nameof(writer));
+ if (!(codeElement.Parent is CodeClass)) throw new InvalidOperationException("the parent of a method should be a class");
var returnType = conventions.GetTypeString(codeElement.ReturnType, codeElement);
var parentClass = codeElement.Parent as CodeClass;
@@ -21,22 +21,31 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri
WriteMethodDocumentation(codeElement, writer);
WriteMethodPrototype(codeElement, writer, returnType, inherits, isVoid);
writer.IncreaseIndent();
- var requestBodyParam = codeElement.Parameters.OfKind(CodeParameterKind.RequestBody);
- var queryStringParam = codeElement.Parameters.OfKind(CodeParameterKind.QueryParameter);
- var headersParam = codeElement.Parameters.OfKind(CodeParameterKind.Headers);
- var optionsParam = codeElement.Parameters.OfKind(CodeParameterKind.Options);
- var requestParams = new RequestParams(requestBodyParam, queryStringParam, headersParam, optionsParam);
- foreach(var parameter in codeElement.Parameters.Where(x => !x.Optional).OrderBy(x => x.Name)) {
+ foreach (var parameter in codeElement.Parameters.Where(x => !x.Optional).OrderBy(x => x.Name))
+ {
var parameterName = parameter.Name.ToFirstCharacterLowerCase();
- if(nameof(String).Equals(parameter.Type.Name, StringComparison.OrdinalIgnoreCase))
+ if (nameof(String).Equals(parameter.Type.Name, StringComparison.OrdinalIgnoreCase))
writer.WriteLine($"if(string.IsNullOrEmpty({parameterName})) throw new ArgumentNullException(nameof({parameterName}));");
else
writer.WriteLine($"_ = {parameterName} ?? throw new ArgumentNullException(nameof({parameterName}));");
}
- switch(codeElement.MethodKind) {
+ HandleMethodKind(codeElement, writer, inherits, parentClass, isVoid);
+ writer.CloseBlock();
+ }
+
+ protected virtual void HandleMethodKind(CodeMethod codeElement, LanguageWriter writer, bool inherits, CodeClass parentClass, bool isVoid)
+ {
+ var returnType = conventions.GetTypeString(codeElement.ReturnType, codeElement);
+ var requestBodyParam = codeElement.Parameters.OfKind(CodeParameterKind.RequestBody);
+ var queryStringParam = codeElement.Parameters.OfKind(CodeParameterKind.QueryParameter);
+ var headersParam = codeElement.Parameters.OfKind(CodeParameterKind.Headers);
+ var optionsParam = codeElement.Parameters.OfKind(CodeParameterKind.Options);
+ var requestParams = new RequestParams(requestBodyParam, queryStringParam, headersParam, optionsParam);
+ switch (codeElement.MethodKind)
+ {
case CodeMethodKind.Serializer:
WriteSerializerBody(inherits, codeElement, parentClass, writer);
- break;
+ break;
case CodeMethodKind.RequestGenerator:
WriteRequestGeneratorBody(codeElement, requestParams, parentClass, writer);
break;
@@ -62,45 +71,55 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri
throw new InvalidOperationException("getters and setters are automatically added on fields in dotnet");
case CodeMethodKind.RequestBuilderBackwardCompatibility:
throw new InvalidOperationException("RequestBuilderBackwardCompatibility is not supported as the request builders are implemented by properties.");
+ case CodeMethodKind.CommandBuilder:
+ var origParams = codeElement.OriginalMethod?.Parameters ?? codeElement.Parameters;
+ requestBodyParam = origParams.OfKind(CodeParameterKind.RequestBody);
+ requestParams = new RequestParams(requestBodyParam, null, null, null);
+ WriteCommandBuilderBody(codeElement, requestParams, isVoid, returnType, writer);
+ break;
default:
writer.WriteLine("return null;");
- break;
+ break;
}
- writer.DecreaseIndent();
- writer.WriteLine("}");
}
private void WriteRequestBuilderBody(CodeClass parentClass, CodeMethod codeElement, LanguageWriter writer)
{
var importSymbol = conventions.GetTypeString(codeElement.ReturnType, parentClass);
conventions.AddRequestBuilderBody(parentClass, importSymbol, writer, prefix: "return ", pathParameters: codeElement.Parameters.Where(x => x.IsOfKind(CodeParameterKind.Path)));
}
- private static void WriteApiConstructorBody(CodeClass parentClass, CodeMethod method, LanguageWriter writer) {
+ private static void WriteApiConstructorBody(CodeClass parentClass, CodeMethod method, LanguageWriter writer)
+ {
var requestAdapterProperty = parentClass.GetPropertyOfKind(CodePropertyKind.RequestAdapter);
var backingStoreParameter = method.Parameters.FirstOrDefault(x => x.IsOfKind(CodeParameterKind.BackingStore));
var requestAdapterPropertyName = requestAdapterProperty.Name.ToFirstCharacterUpperCase();
WriteSerializationRegistration(method.SerializerModules, writer, "RegisterDefaultSerializer");
WriteSerializationRegistration(method.DeserializerModules, writer, "RegisterDefaultDeserializer");
writer.WriteLine($"{requestAdapterPropertyName}.BaseUrl = \"{method.BaseUrl}\";");
- if(backingStoreParameter != null)
+ if (backingStoreParameter != null)
writer.WriteLine($"{requestAdapterPropertyName}.EnableBackingStore({backingStoreParameter.Name});");
}
- private static void WriteSerializationRegistration(List serializationClassNames, LanguageWriter writer, string methodName) {
- if(serializationClassNames != null)
- foreach(var serializationClassName in serializationClassNames)
+ private static void WriteSerializationRegistration(List serializationClassNames, LanguageWriter writer, string methodName)
+ {
+ if (serializationClassNames != null)
+ foreach (var serializationClassName in serializationClassNames)
writer.WriteLine($"ApiClientBuilder.{methodName}<{serializationClassName}>();");
}
- private void WriteConstructorBody(CodeClass parentClass, CodeMethod currentMethod, LanguageWriter writer) {
- foreach(var propWithDefault in parentClass
+ private void WriteConstructorBody(CodeClass parentClass, CodeMethod currentMethod, LanguageWriter writer)
+ {
+ foreach (var propWithDefault in parentClass
.Properties
.Where(x => !string.IsNullOrEmpty(x.DefaultValue))
.OrderByDescending(x => x.PropertyKind)
- .ThenBy(x => x.Name)) {
+ .ThenBy(x => x.Name))
+ {
writer.WriteLine($"{propWithDefault.Name.ToFirstCharacterUpperCase()} = {propWithDefault.DefaultValue};");
}
- if(parentClass.IsOfKind(CodeClassKind.RequestBuilder)) {
- if(currentMethod.IsOfKind(CodeMethodKind.Constructor)) {
+ if (parentClass.IsOfKind(CodeClassKind.RequestBuilder))
+ {
+ if (currentMethod.IsOfKind(CodeMethodKind.Constructor))
+ {
var pathParametersParam = currentMethod.Parameters.FirstOrDefault(x => x.IsOfKind(CodeParameterKind.PathParameters));
- conventions.AddParametersAssignment(writer,
+ conventions.AddParametersAssignment(writer,
pathParametersParam.Type,
pathParametersParam.Name.ToFirstCharacterLowerCase(),
currentMethod.Parameters
@@ -109,7 +128,8 @@ private void WriteConstructorBody(CodeClass parentClass, CodeMethod currentMetho
.ToArray());
AssignPropertyFromParameter(parentClass, currentMethod, CodeParameterKind.PathParameters, CodePropertyKind.PathParameters, writer, conventions.TempDictionaryVarName);
}
- else if(currentMethod.IsOfKind(CodeMethodKind.RawUrlConstructor)) {
+ else if (currentMethod.IsOfKind(CodeMethodKind.RawUrlConstructor))
+ {
var pathParametersProp = parentClass.GetPropertyOfKind(CodePropertyKind.PathParameters);
var rawUrlParam = currentMethod.Parameters.FirstOrDefault(x => x.IsOfKind(CodeParameterKind.RawUrl));
conventions.AddParametersAssignment(writer,
@@ -121,35 +141,41 @@ private void WriteConstructorBody(CodeClass parentClass, CodeMethod currentMetho
AssignPropertyFromParameter(parentClass, currentMethod, CodeParameterKind.RequestAdapter, CodePropertyKind.RequestAdapter, writer);
}
}
- private static void AssignPropertyFromParameter(CodeClass parentClass, CodeMethod currentMethod, CodeParameterKind parameterKind, CodePropertyKind propertyKind, LanguageWriter writer, string variableName = default) {
+ private static void AssignPropertyFromParameter(CodeClass parentClass, CodeMethod currentMethod, CodeParameterKind parameterKind, CodePropertyKind propertyKind, LanguageWriter writer, string variableName = default)
+ {
var property = parentClass.GetPropertyOfKind(propertyKind);
- if(property != null) {
+ if (property != null)
+ {
var parameter = currentMethod.Parameters.FirstOrDefault(x => x.IsOfKind(parameterKind));
- if(!string.IsNullOrEmpty(variableName))
+ if (!string.IsNullOrEmpty(variableName))
writer.WriteLine($"{property.Name.ToFirstCharacterUpperCase()} = {variableName};");
- else if(parameter != null)
+ else if (parameter != null)
writer.WriteLine($"{property.Name.ToFirstCharacterUpperCase()} = {parameter.Name};");
}
}
- private void WriteDeserializerBody(bool shouldHide, CodeMethod codeElement, CodeClass parentClass, LanguageWriter writer) {
+ private void WriteDeserializerBody(bool shouldHide, CodeMethod codeElement, CodeClass parentClass, LanguageWriter writer)
+ {
var parentSerializationInfo = shouldHide ? $"(base.{codeElement.Name.ToFirstCharacterUpperCase()}())" : string.Empty;
writer.WriteLine($"return new Dictionary>{parentSerializationInfo} {{");
writer.IncreaseIndent();
- foreach(var otherProp in parentClass
+ foreach (var otherProp in parentClass
.Properties
.Where(x => x.IsOfKind(CodePropertyKind.Custom))
- .OrderBy(x => x.Name)) {
+ .OrderBy(x => x.Name))
+ {
writer.WriteLine($"{{\"{otherProp.SerializationName ?? otherProp.Name.ToFirstCharacterLowerCase()}\", (o,n) => {{ (o as {parentClass.Name.ToFirstCharacterUpperCase()}).{otherProp.Name.ToFirstCharacterUpperCase()} = n.{GetDeserializationMethodName(otherProp.Type, codeElement)}(); }} }},");
}
writer.DecreaseIndent();
writer.WriteLine("};");
}
- private string GetDeserializationMethodName(CodeTypeBase propType, CodeMethod method) {
+ private string GetDeserializationMethodName(CodeTypeBase propType, CodeMethod method)
+ {
var isCollection = propType.CollectionKind != CodeTypeBase.CodeTypeCollectionKind.None;
var propertyType = conventions.GetTypeString(propType, method, false);
- if(propType is CodeType currentType) {
- if(isCollection)
- if(currentType.TypeDefinition == null)
+ if (propType is CodeType currentType)
+ {
+ if (isCollection)
+ if (currentType.TypeDefinition == null)
return $"GetCollectionOfPrimitiveValues<{propertyType}>().ToList";
else if (currentType.TypeDefinition is CodeEnum enumType)
return $"GetCollectionOfEnumValues<{enumType.Name.ToFirstCharacterUpperCase()}>().ToList";
@@ -165,23 +191,26 @@ _ when conventions.IsPrimitiveType(propertyType) => $"Get{propertyType.TrimEnd(C
_ => $"GetObjectValue<{propertyType.ToFirstCharacterUpperCase()}>",
};
}
- private void WriteRequestExecutorBody(CodeMethod codeElement, RequestParams requestParams, bool isVoid, string returnType, LanguageWriter writer) {
- if(codeElement.HttpMethod == null) throw new InvalidOperationException("http method cannot be null");
-
+ protected void WriteRequestExecutorBody(CodeMethod codeElement, RequestParams requestParams, bool isVoid, string returnType, LanguageWriter writer)
+ {
+ if (codeElement.HttpMethod == null) throw new InvalidOperationException("http method cannot be null");
+
var isStream = conventions.StreamTypeName.Equals(returnType, StringComparison.OrdinalIgnoreCase);
var generatorMethodName = (codeElement.Parent as CodeClass)
.Methods
.FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestGenerator) && x.HttpMethod == codeElement.HttpMethod)
?.Name;
var parametersList = new CodeParameter[] { requestParams.requestBody, requestParams.queryString, requestParams.headers, requestParams.options }
- .Select(x => x?.Name).Where(x => x != null).Aggregate((x,y) => $"{x}, {y}");
+ .Select(x => x?.Name).Where(x => x != null).Aggregate((x, y) => $"{x}, {y}");
writer.WriteLine($"var requestInfo = {generatorMethodName}({parametersList});");
var errorMappingVarName = "default";
- if(codeElement.ErrorMappings.Any()) {
+ if (codeElement.ErrorMappings.Any())
+ {
errorMappingVarName = "errorMapping";
writer.WriteLine($"var {errorMappingVarName} = new Dictionary> {{");
writer.IncreaseIndent();
- foreach(var errorMapping in codeElement.ErrorMappings) {
+ foreach (var errorMapping in codeElement.ErrorMappings)
+ {
writer.WriteLine($"{{\"{errorMapping.Key.ToUpperInvariant()}\", () => new {errorMapping.Value.Name.ToFirstCharacterUpperCase()}()}},");
}
writer.CloseBlock("};");
@@ -189,9 +218,10 @@ private void WriteRequestExecutorBody(CodeMethod codeElement, RequestParams requ
writer.WriteLine($"{(isVoid ? string.Empty : "return ")}await RequestAdapter.{GetSendRequestMethodName(isVoid, isStream, codeElement.ReturnType.IsCollection, returnType)}(requestInfo, responseHandler, {errorMappingVarName}, cancellationToken);");
}
private const string RequestInfoVarName = "requestInfo";
- private void WriteRequestGeneratorBody(CodeMethod codeElement, RequestParams requestParams, CodeClass currentClass, LanguageWriter writer) {
- if(codeElement.HttpMethod == null) throw new InvalidOperationException("http method cannot be null");
-
+ private void WriteRequestGeneratorBody(CodeMethod codeElement, RequestParams requestParams, CodeClass currentClass, LanguageWriter writer)
+ {
+ if (codeElement.HttpMethod == null) throw new InvalidOperationException("http method cannot be null");
+
var operationName = codeElement.HttpMethod.ToString();
var urlTemplateParamsProperty = currentClass.GetPropertyOfKind(CodePropertyKind.PathParameters);
var urlTemplateProperty = currentClass.GetPropertyOfKind(CodePropertyKind.UrlTemplate);
@@ -203,13 +233,15 @@ private void WriteRequestGeneratorBody(CodeMethod codeElement, RequestParams req
$"PathParameters = {GetPropertyCall(urlTemplateParamsProperty, "string.Empty")},");
writer.DecreaseIndent();
writer.WriteLine("};");
- if(requestParams.requestBody != null) {
- if(requestParams.requestBody.Type.Name.Equals(conventions.StreamTypeName, StringComparison.OrdinalIgnoreCase))
+ if (requestParams.requestBody != null)
+ {
+ if (requestParams.requestBody.Type.Name.Equals(conventions.StreamTypeName, StringComparison.OrdinalIgnoreCase))
writer.WriteLine($"{RequestInfoVarName}.SetStreamContent({requestParams.requestBody.Name});");
else
writer.WriteLine($"{RequestInfoVarName}.SetContentFromParsable({requestAdapterProperty.Name.ToFirstCharacterUpperCase()}, \"{codeElement.ContentType}\", {requestParams.requestBody.Name});");
}
- if(requestParams.queryString != null) {
+ if (requestParams.queryString != null)
+ {
writer.WriteLine($"if ({requestParams.queryString.Name} != null) {{");
writer.IncreaseIndent();
writer.WriteLines($"var qParams = new {operationName?.ToFirstCharacterUpperCase()}QueryParameters();",
@@ -218,84 +250,98 @@ private void WriteRequestGeneratorBody(CodeMethod codeElement, RequestParams req
writer.DecreaseIndent();
writer.WriteLine("}");
}
- if(requestParams.headers != null)
+ if (requestParams.headers != null)
writer.WriteLine($"{requestParams.headers.Name}?.Invoke({RequestInfoVarName}.Headers);");
- if(requestParams.options != null)
+ if (requestParams.options != null)
writer.WriteLine($"{RequestInfoVarName}.AddRequestOptions({requestParams.options.Name}?.ToArray());");
writer.WriteLine($"return {RequestInfoVarName};");
}
private static string GetPropertyCall(CodeProperty property, string defaultValue) => property == null ? defaultValue : $"{property.Name.ToFirstCharacterUpperCase()}";
- private void WriteSerializerBody(bool shouldHide, CodeMethod method, CodeClass parentClass, LanguageWriter writer) {
+ private void WriteSerializerBody(bool shouldHide, CodeMethod method, CodeClass parentClass, LanguageWriter writer)
+ {
var additionalDataProperty = parentClass.GetPropertyOfKind(CodePropertyKind.AdditionalData);
- if(shouldHide)
+ if (shouldHide)
writer.WriteLine("base.Serialize(writer);");
- foreach(var otherProp in parentClass
+ foreach (var otherProp in parentClass
.Properties
.Where(x => x.IsOfKind(CodePropertyKind.Custom))
- .OrderBy(x => x.Name)) {
+ .OrderBy(x => x.Name))
+ {
writer.WriteLine($"writer.{GetSerializationMethodName(otherProp.Type, method)}(\"{otherProp.SerializationName ?? otherProp.Name.ToFirstCharacterLowerCase()}\", {otherProp.Name.ToFirstCharacterUpperCase()});");
}
- if(additionalDataProperty != null)
+ if (additionalDataProperty != null)
writer.WriteLine($"writer.WriteAdditionalData({additionalDataProperty.Name});");
}
- private string GetSendRequestMethodName(bool isVoid, bool isStream, bool isCollection, string returnType) {
- if(isVoid) return "SendNoContentAsync";
- else if(isStream || conventions.IsPrimitiveType(returnType))
- if(isCollection)
+
+ protected virtual void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams requestParams, bool isVoid, string returnType, LanguageWriter writer)
+ {
+ throw new InvalidOperationException("CommandBuilder methods are not implemented in this SDK. They're currently only supported in the shell language.");
+ }
+
+ protected string GetSendRequestMethodName(bool isVoid, bool isStream, bool isCollection, string returnType)
+ {
+ if (isVoid) return "SendNoContentAsync";
+ else if (isStream || conventions.IsPrimitiveType(returnType))
+ if (isCollection)
return $"SendPrimitiveCollectionAsync<{returnType.StripArraySuffix()}>";
else
return $"SendPrimitiveAsync<{returnType}>";
else if (isCollection) return $"SendCollectionAsync<{returnType.StripArraySuffix()}>";
else return $"SendAsync<{returnType}>";
}
- private void WriteMethodDocumentation(CodeMethod code, LanguageWriter writer) {
+ private void WriteMethodDocumentation(CodeMethod code, LanguageWriter writer)
+ {
var isDescriptionPresent = !string.IsNullOrEmpty(code.Description);
var parametersWithDescription = code.Parameters.Where(x => !string.IsNullOrEmpty(code.Description));
- if (isDescriptionPresent || parametersWithDescription.Any()) {
+ if (isDescriptionPresent || parametersWithDescription.Any())
+ {
writer.WriteLine($"{conventions.DocCommentPrefix}");
- if(isDescriptionPresent)
+ if (isDescriptionPresent)
writer.WriteLine($"{conventions.DocCommentPrefix}{code.Description}");
- foreach(var paramWithDescription in parametersWithDescription.OrderBy(x => x.Name))
+ foreach (var paramWithDescription in parametersWithDescription.OrderBy(x => x.Name))
writer.WriteLine($"{conventions.DocCommentPrefix}{paramWithDescription.Description}");
writer.WriteLine($"{conventions.DocCommentPrefix}");
}
}
private static readonly CodeParameterOrderComparer parameterOrderComparer = new();
- private void WriteMethodPrototype(CodeMethod code, LanguageWriter writer, string returnType, bool inherits, bool isVoid) {
+ private void WriteMethodPrototype(CodeMethod code, LanguageWriter writer, string returnType, bool inherits, bool isVoid)
+ {
var staticModifier = code.IsStatic ? "static " : string.Empty;
var hideModifier = inherits && code.IsSerializationMethod ? "new " : string.Empty;
var genericTypePrefix = isVoid ? string.Empty : "<";
- var genericTypeSuffix = code.IsAsync && !isVoid ? ">": string.Empty;
+ var genericTypeSuffix = code.IsAsync && !isVoid ? ">" : string.Empty;
var isConstructor = code.IsOfKind(CodeMethodKind.Constructor, CodeMethodKind.ClientConstructor, CodeMethodKind.RawUrlConstructor);
var asyncPrefix = code.IsAsync ? "async Task" + genericTypePrefix : string.Empty;
var voidCorrectedTaskReturnType = code.IsAsync && isVoid ? string.Empty : returnType;
- if(code.ReturnType.IsArray && code.IsOfKind(CodeMethodKind.RequestExecutor))
+ if (code.ReturnType.IsArray && code.IsOfKind(CodeMethodKind.RequestExecutor))
voidCorrectedTaskReturnType = $"IEnumerable<{voidCorrectedTaskReturnType.StripArraySuffix()}>";
// TODO: Task type should be moved into the refiner
var completeReturnType = isConstructor ?
string.Empty :
$"{asyncPrefix}{voidCorrectedTaskReturnType}{genericTypeSuffix} ";
var baseSuffix = string.Empty;
- if(isConstructor && inherits)
+ if (isConstructor && inherits)
baseSuffix = " : base()";
- var parameters = string.Join(", ", code.Parameters.OrderBy(x => x, parameterOrderComparer).Select(p=> conventions.GetParameterSignature(p, code)).ToList());
+ var parameters = string.Join(", ", code.Parameters.OrderBy(x => x, parameterOrderComparer).Select(p => conventions.GetParameterSignature(p, code)).ToList());
var methodName = isConstructor ? code.Parent.Name.ToFirstCharacterUpperCase() : code.Name.ToFirstCharacterUpperCase();
writer.WriteLine($"{conventions.GetAccessModifier(code.Access)} {staticModifier}{hideModifier}{completeReturnType}{methodName}({parameters}){baseSuffix} {{");
}
- private string GetSerializationMethodName(CodeTypeBase propType, CodeMethod method) {
+ private string GetSerializationMethodName(CodeTypeBase propType, CodeMethod method)
+ {
var isCollection = propType.CollectionKind != CodeTypeBase.CodeTypeCollectionKind.None;
var propertyType = conventions.GetTypeString(propType, method, false);
- if(propType is CodeType currentType) {
- if(isCollection)
- if(currentType.TypeDefinition == null)
+ if (propType is CodeType currentType)
+ {
+ if (isCollection)
+ if (currentType.TypeDefinition == null)
return $"WriteCollectionOfPrimitiveValues<{propertyType}>";
- else if(currentType.TypeDefinition is CodeEnum enumType)
+ else if (currentType.TypeDefinition is CodeEnum enumType)
return $"WriteCollectionOfEnumValues<{enumType.Name.ToFirstCharacterUpperCase()}>";
else
return $"WriteCollectionOfObjectValues<{propertyType}>";
else if (currentType.TypeDefinition is CodeEnum enumType)
return $"WriteEnumValue<{enumType.Name.ToFirstCharacterUpperCase()}>";
-
+
}
return propertyType switch
{
diff --git a/src/Kiota.Builder/Writers/Go/GoWriter.cs b/src/Kiota.Builder/Writers/Go/GoWriter.cs
index dd90d6b4c8..185efd13cd 100644
--- a/src/Kiota.Builder/Writers/Go/GoWriter.cs
+++ b/src/Kiota.Builder/Writers/Go/GoWriter.cs
@@ -4,11 +4,11 @@ public GoWriter(string rootPath, string clientNamespaceName)
{
PathSegmenter = new GoPathSegmenter(rootPath, clientNamespaceName);
var conventionService = new GoConventionService();
- AddCodeElementWriter(new CodeClassDeclarationWriter(conventionService));
- AddCodeElementWriter(new CodeClassEndWriter());
- AddCodeElementWriter(new CodePropertyWriter(conventionService));
- AddCodeElementWriter(new CodeEnumWriter(conventionService));
- AddCodeElementWriter(new CodeMethodWriter(conventionService));
+ AddOrReplaceCodeElementWriter(new CodeClassDeclarationWriter(conventionService));
+ AddOrReplaceCodeElementWriter(new CodeClassEndWriter());
+ AddOrReplaceCodeElementWriter(new CodePropertyWriter(conventionService));
+ AddOrReplaceCodeElementWriter(new CodeEnumWriter(conventionService));
+ AddOrReplaceCodeElementWriter(new CodeMethodWriter(conventionService));
}
}
}
diff --git a/src/Kiota.Builder/Writers/Java/JavaWriter.cs b/src/Kiota.Builder/Writers/Java/JavaWriter.cs
index b34b5d0441..537a2af81c 100644
--- a/src/Kiota.Builder/Writers/Java/JavaWriter.cs
+++ b/src/Kiota.Builder/Writers/Java/JavaWriter.cs
@@ -6,12 +6,12 @@ public JavaWriter(string rootPath, string clientNamespaceName)
{
PathSegmenter = new JavaPathSegmenter(rootPath, clientNamespaceName);
var conventionService = new JavaConventionService();
- AddCodeElementWriter(new CodeClassDeclarationWriter(conventionService));
- AddCodeElementWriter(new CodeClassEndWriter());
- AddCodeElementWriter(new CodeEnumWriter(conventionService));
- AddCodeElementWriter(new CodeMethodWriter(conventionService));
- AddCodeElementWriter(new CodePropertyWriter(conventionService));
- AddCodeElementWriter(new CodeTypeWriter(conventionService));
+ AddOrReplaceCodeElementWriter(new CodeClassDeclarationWriter(conventionService));
+ AddOrReplaceCodeElementWriter(new CodeClassEndWriter());
+ AddOrReplaceCodeElementWriter(new CodeEnumWriter(conventionService));
+ AddOrReplaceCodeElementWriter(new CodeMethodWriter(conventionService));
+ AddOrReplaceCodeElementWriter(new CodePropertyWriter(conventionService));
+ AddOrReplaceCodeElementWriter(new CodeTypeWriter(conventionService));
}
}
}
diff --git a/src/Kiota.Builder/Writers/LanguageWriter.cs b/src/Kiota.Builder/Writers/LanguageWriter.cs
index c1dbda7265..34df4c970d 100644
--- a/src/Kiota.Builder/Writers/LanguageWriter.cs
+++ b/src/Kiota.Builder/Writers/LanguageWriter.cs
@@ -7,6 +7,7 @@
using Kiota.Builder.Writers.Go;
using Kiota.Builder.Writers.Java;
using Kiota.Builder.Writers.Ruby;
+using Kiota.Builder.Writers.Shell;
using Kiota.Builder.Writers.TypeScript;
using Kiota.Builder.Writers.Php;
@@ -111,8 +112,9 @@ public void Write(T code) where T : CodeElement
else if(!(code is CodeClass) && !(code is CodeNamespace.BlockDeclaration) && !(code is CodeNamespace.BlockEnd))
throw new InvalidOperationException($"Dispatcher missing for type {code.GetType()}");
}
- protected void AddCodeElementWriter(ICodeElementWriter writer) where T: CodeElement {
- Writers.Add(typeof(T), writer);
+ protected void AddOrReplaceCodeElementWriter(ICodeElementWriter writer) where T: CodeElement {
+ if (!Writers.TryAdd(typeof(T), writer))
+ Writers[typeof(T)] = writer;
}
private readonly Dictionary Writers = new(); // we have to type as object because dotnet doesn't have type capture i.e eq for `? extends CodeElement`
public static LanguageWriter GetLanguageWriter(GenerationLanguage language, string outputPath, string clientNamespaceName) {
@@ -124,6 +126,7 @@ public static LanguageWriter GetLanguageWriter(GenerationLanguage language, stri
GenerationLanguage.Ruby => new RubyWriter(outputPath, clientNamespaceName),
GenerationLanguage.PHP => new PhpWriter(outputPath, clientNamespaceName),
GenerationLanguage.Go => new GoWriter(outputPath, clientNamespaceName),
+ GenerationLanguage.Shell => new ShellWriter(outputPath, clientNamespaceName),
_ => throw new InvalidEnumArgumentException($"{language} language currently not supported."),
};
}
diff --git a/src/Kiota.Builder/Writers/Php/PhpWriter.cs b/src/Kiota.Builder/Writers/Php/PhpWriter.cs
index eabdd71465..5e241dff8f 100644
--- a/src/Kiota.Builder/Writers/Php/PhpWriter.cs
+++ b/src/Kiota.Builder/Writers/Php/PhpWriter.cs
@@ -6,11 +6,11 @@ public PhpWriter(string rootPath, string clientNamespaceName, bool useBackingSto
{
PathSegmenter = new PhpPathSegmenter(rootPath, clientNamespaceName);
var conventionService = new PhpConventionService();
- AddCodeElementWriter(new CodeClassDeclarationWriter(conventionService));
- AddCodeElementWriter(new CodePropertyWriter(conventionService));
- AddCodeElementWriter(new CodeMethodWriter(conventionService));
- AddCodeElementWriter(new CodeClassEndWriter());
- AddCodeElementWriter(new CodeEnumWriter(conventionService));
+ AddOrReplaceCodeElementWriter(new CodeClassDeclarationWriter(conventionService));
+ AddOrReplaceCodeElementWriter(new CodePropertyWriter(conventionService));
+ AddOrReplaceCodeElementWriter(new CodeMethodWriter(conventionService));
+ AddOrReplaceCodeElementWriter(new CodeClassEndWriter());
+ AddOrReplaceCodeElementWriter(new CodeEnumWriter(conventionService));
}
}
}
diff --git a/src/Kiota.Builder/Writers/Ruby/RubyWriter.cs b/src/Kiota.Builder/Writers/Ruby/RubyWriter.cs
index 32a7ee8acc..16ebc32a32 100644
--- a/src/Kiota.Builder/Writers/Ruby/RubyWriter.cs
+++ b/src/Kiota.Builder/Writers/Ruby/RubyWriter.cs
@@ -6,12 +6,12 @@ public RubyWriter(string rootPath, string clientNamespaceName)
{
PathSegmenter = new RubyPathSegmenter(rootPath, clientNamespaceName);
var conventionService = new RubyConventionService();
- AddCodeElementWriter(new CodeClassDeclarationWriter(conventionService, clientNamespaceName));
- AddCodeElementWriter(new CodeClassEndWriter(conventionService));
- AddCodeElementWriter(new CodeNamespaceWriter(conventionService));
- AddCodeElementWriter(new CodeEnumWriter(conventionService));
- AddCodeElementWriter(new CodeMethodWriter(conventionService));
- AddCodeElementWriter(new CodePropertyWriter(conventionService));
+ AddOrReplaceCodeElementWriter(new CodeClassDeclarationWriter(conventionService, clientNamespaceName));
+ AddOrReplaceCodeElementWriter(new CodeClassEndWriter(conventionService));
+ AddOrReplaceCodeElementWriter(new CodeNamespaceWriter(conventionService));
+ AddOrReplaceCodeElementWriter(new CodeEnumWriter(conventionService));
+ AddOrReplaceCodeElementWriter(new CodeMethodWriter(conventionService));
+ AddOrReplaceCodeElementWriter(new CodePropertyWriter(conventionService));
}
}
}
diff --git a/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs
new file mode 100644
index 0000000000..3fb24e70e0
--- /dev/null
+++ b/src/Kiota.Builder/Writers/Shell/ShellCodeMethodWriter.cs
@@ -0,0 +1,441 @@
+using System;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using Kiota.Builder.Writers.CSharp;
+using Kiota.Builder.Extensions;
+using System.Collections.Generic;
+
+namespace Kiota.Builder.Writers.Shell
+{
+ class ShellCodeMethodWriter : CodeMethodWriter
+ {
+ private static Regex delimitedRegex = new Regex("(?<=[a-z])[-_\\.]+([A-Za-z])", RegexOptions.Compiled);
+ private static Regex camelCaseRegex = new Regex("(?<=[a-z])([A-Z])", RegexOptions.Compiled);
+ private static Regex identifierRegex = new Regex("(?:[-_\\.]([a-zA-Z]))", RegexOptions.Compiled);
+ private static Regex uppercaseRegex = new Regex("([A-Z])", RegexOptions.Compiled);
+ private const string cancellationTokenParamType = "CancellationToken";
+ private const string cancellationTokenParamName = "cancellationToken";
+ private const string consoleParamType = "IConsole";
+ private const string consoleParamName = "console";
+ private const string fileParamType = "FileInfo";
+ private const string fileParamName = "file";
+ private const string outputFormatParamType = "FormatterType";
+ private const string outputFormatParamName = "output";
+ private const string outputFormatterFactoryParamType = "IOutputFormatterFactory";
+ private const string outputFormatterFactoryParamName = "outputFormatterFactory";
+
+ public ShellCodeMethodWriter(CSharpConventionService conventionService) : base(conventionService)
+ {
+ }
+
+ protected override void WriteCommandBuilderBody(CodeMethod codeElement, RequestParams requestParams, bool isVoid, string returnType, LanguageWriter writer)
+ {
+ var parent = codeElement.Parent as CodeClass;
+ var classMethods = parent.Methods;
+ var name = codeElement.SimpleName;
+ name = uppercaseRegex.Replace(name, "-$1").TrimStart('-').ToLower();
+
+ if (codeElement.HttpMethod == null)
+ {
+ // Build method
+ // Puts together the BuildXXCommand objects. Needs a nav property name e.g. users
+ // Command("users") -> Command("get")
+ if (string.IsNullOrWhiteSpace(name))
+ {
+ // BuildCommand function
+ WriteUnnamedBuildCommand(codeElement, writer, parent, classMethods);
+ }
+ else
+ {
+ WriteContainerCommand(codeElement, writer, parent, name);
+ }
+ } else
+ {
+ WriteExecutableCommand(codeElement, requestParams, writer, name);
+ }
+ }
+
+ private void WriteExecutableCommand(CodeMethod codeElement, RequestParams requestParams, LanguageWriter writer, string name)
+ {
+ var generatorMethod = (codeElement.Parent as CodeClass)
+ .Methods
+ .FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestGenerator) && x.HttpMethod == codeElement.HttpMethod);
+ var pathAndQueryParams = generatorMethod.PathAndQueryParameters;
+ var originalMethod = codeElement.OriginalMethod;
+ var origParams = originalMethod.Parameters;
+ var parametersList = pathAndQueryParams?.Where(p => !string.IsNullOrWhiteSpace(p.Name))?.ToList() ?? new List();
+ if (origParams.Any(p => p.IsOfKind(CodeParameterKind.RequestBody)))
+ {
+ parametersList.Add(origParams.OfKind(CodeParameterKind.RequestBody));
+ }
+ writer.WriteLine($"var command = new Command(\"{name}\");");
+ WriteCommandDescription(codeElement, writer);
+ writer.WriteLine("// Create options for all the parameters");
+ // investigate exploding query params
+ // Check the possible formatting options for headers in a cli.
+ // -h A=b -h
+ // -h A:B,B:C
+ // -h {"A": "B"}
+ var availableOptions = WriteExecutableCommandOptions(writer, parametersList);
+
+ var paramTypes = parametersList.Select(x =>
+ {
+ var codeType = x.Type as CodeType;
+ if (x.IsOfKind(CodeParameterKind.RequestBody) && codeType.TypeDefinition is CodeClass)
+ {
+ return "string";
+ }
+ else if (conventions.StreamTypeName.Equals(x.Type?.Name, StringComparison.OrdinalIgnoreCase))
+ {
+ return "FileInfo";
+ }
+
+ return conventions.GetTypeString(x.Type, x);
+ }).ToList();
+ var paramNames = parametersList.Select(x => NormalizeToIdentifier(x.Name)).ToList();
+ var isHandlerVoid = conventions.VoidTypeName.Equals(originalMethod.ReturnType.Name, StringComparison.OrdinalIgnoreCase);
+ var returnType = conventions.GetTypeString(originalMethod.ReturnType, originalMethod);
+ if (conventions.StreamTypeName.Equals(returnType, StringComparison.OrdinalIgnoreCase))
+ {
+ var fileOptionName = "fileOption";
+ writer.WriteLine($"var {fileOptionName} = new Option<{fileParamType}>(\"--{fileParamName}\");");
+ writer.WriteLine($"command.AddOption({fileOptionName});");
+ paramTypes.Add(fileParamType);
+ paramNames.Add(fileParamName);
+ availableOptions.Add(fileOptionName);
+ }
+
+ // Add output type param
+ if (!isHandlerVoid)
+ {
+ var outputOptionName = "outputOption";
+ writer.WriteLine($"var {outputOptionName} = new Option<{outputFormatParamType}>(\"--{outputFormatParamName}\", () => FormatterType.JSON){{");
+ writer.IncreaseIndent();
+ writer.WriteLine("IsRequired = true");
+ writer.CloseBlock("};");
+ writer.WriteLine($"command.AddOption({outputOptionName});");
+ paramTypes.Add(outputFormatParamType);
+ paramNames.Add(outputFormatParamName);
+ availableOptions.Add(outputOptionName);
+ }
+
+ // Add output formatter factory param
+ paramTypes.Add(outputFormatterFactoryParamType);
+ paramNames.Add(outputFormatterFactoryParamName);
+
+ // Add console param
+ paramTypes.Add(consoleParamType);
+ paramNames.Add(consoleParamName);
+
+ // Add CancellationToken param
+ paramTypes.Add(cancellationTokenParamType);
+ paramNames.Add(cancellationTokenParamName);
+
+ var zipped = paramTypes.Zip(paramNames);
+ var projected = zipped.Select((x, y) => $"{x.First} {x.Second}");
+ var handlerParams = string.Join(", ", projected);
+ writer.WriteLine($"command.SetHandler(async ({handlerParams}) => {{");
+ writer.IncreaseIndent();
+ WriteCommandHandlerBody(originalMethod, requestParams, isHandlerVoid, returnType, writer);
+ // Get request generator method. To call it + get path & query parameters see WriteRequestExecutorBody in CSharp
+ if (isHandlerVoid)
+ {
+ writer.WriteLine($"{consoleParamName}.WriteLine(\"Success\");");
+ }
+ else
+ {
+ var type = originalMethod.ReturnType as CodeType;
+ var typeString = conventions.GetTypeString(type, originalMethod);
+ var formatterVar = "formatter";
+
+ writer.WriteLine($"var {formatterVar} = {outputFormatterFactoryParamName}.GetFormatter({outputFormatParamName});");
+ if (typeString != "Stream")
+ {
+ writer.WriteLine($"{formatterVar}.WriteOutput(response, {consoleParamName});");
+ }
+ else
+ {
+ writer.WriteLine($"if ({fileParamName} == null) {{");
+ writer.IncreaseIndent();
+ writer.WriteLine($"{formatterVar}.WriteOutput(response, {consoleParamName});");
+ writer.CloseBlock();
+ writer.WriteLine("else {");
+ writer.IncreaseIndent();
+ writer.WriteLine($"using var writeStream = {fileParamName}.OpenWrite();");
+ writer.WriteLine("await response.CopyToAsync(writeStream);");
+ writer.WriteLine($"{consoleParamName}.WriteLine($\"Content written to {{{fileParamName}.FullName}}.\");");
+ writer.CloseBlock();
+ }
+ }
+ writer.DecreaseIndent();
+ var delimiter = "";
+ if (availableOptions.Any())
+ {
+ delimiter = ", ";
+ }
+ writer.WriteLine($"}}{delimiter}{string.Join(", ", availableOptions)});");
+ writer.WriteLine("return command;");
+ }
+
+ private List WriteExecutableCommandOptions(LanguageWriter writer, List parametersList)
+ {
+ var availableOptions = new List();
+ foreach (var option in parametersList)
+ {
+ var type = option.Type as CodeType;
+ var optionName = $"{NormalizeToIdentifier(option.Name)}Option";
+ var optionType = conventions.GetTypeString(option.Type, option);
+ if (option.ParameterKind == CodeParameterKind.RequestBody && type.TypeDefinition is CodeClass) optionType = "string";
+
+ // Binary body handling
+ if (option.IsOfKind(CodeParameterKind.RequestBody) && conventions.StreamTypeName.Equals(option.Type?.Name, StringComparison.OrdinalIgnoreCase))
+ {
+ option.Name = "file";
+ }
+
+ var optionBuilder = new StringBuilder("new Option");
+ if (!String.IsNullOrEmpty(optionType))
+ {
+ optionBuilder.Append($"<{optionType}>");
+ }
+ optionBuilder.Append("(\"");
+ if (option.Name.Length > 1) optionBuilder.Append('-');
+ optionBuilder.Append($"-{NormalizeToOption(option.Name)}\"");
+ if (option.DefaultValue != null)
+ {
+ var defaultValue = optionType == "string" ? $"\"{option.DefaultValue}\"" : option.DefaultValue;
+ optionBuilder.Append($", getDefaultValue: ()=> {defaultValue}");
+ }
+
+ if (!string.IsNullOrEmpty(option.Description))
+ {
+ optionBuilder.Append($", description: \"{option.Description}\"");
+ }
+
+ optionBuilder.Append(") {");
+ var strValue = $"{optionBuilder}";
+ writer.WriteLine($"var {optionName} = {strValue}");
+ writer.IncreaseIndent();
+ var isRequired = !option.Optional || option.IsOfKind(CodeParameterKind.Path);
+
+ if (option.Type.IsCollection)
+ {
+ var arity = isRequired ? "OneOrMore" : "ZeroOrMore";
+ writer.WriteLine($"Arity = ArgumentArity.{arity}");
+ }
+
+ writer.DecreaseIndent();
+ writer.WriteLine("};");
+ writer.WriteLine($"{optionName}.IsRequired = {isRequired.ToString().ToFirstCharacterLowerCase()};");
+ writer.WriteLine($"command.AddOption({optionName});");
+ availableOptions.Add(optionName);
+ }
+
+ return availableOptions;
+ }
+
+ private static void WriteCommandDescription(CodeMethod codeElement, LanguageWriter writer)
+ {
+ if (!string.IsNullOrWhiteSpace(codeElement.Description))
+ writer.WriteLine($"command.Description = \"{codeElement.Description}\";");
+ }
+
+ private void WriteContainerCommand(CodeMethod codeElement, LanguageWriter writer, CodeClass parent, string name)
+ {
+ writer.WriteLine($"var command = new Command(\"{name}\");");
+ WriteCommandDescription(codeElement, writer);
+
+ if ((codeElement.AccessedProperty?.Type) is CodeType codeReturnType)
+ {
+ // Include namespace to avoid type ambiguity on similarly named classes. Currently, if we have namespaces A and A.B where both namespaces have type T,
+ // Trying to use type A.B.T in namespace A without using the fully qualified name will break the build.
+ var targetClass = string.Join(".", codeReturnType.TypeDefinition.Parent.Name, conventions.GetTypeString(codeReturnType, codeElement));
+ var builderMethods = codeReturnType.TypeDefinition.GetChildElements(true).OfType()
+ .Where(m => m.IsOfKind(CodeMethodKind.CommandBuilder))
+ .OrderBy(m => m.Name)
+ .ThenBy(m => m.ReturnType.IsCollection);
+ conventions.AddRequestBuilderBody(parent, targetClass, writer, prefix: "var builder = ", pathParameters: codeElement.Parameters.Where(x => x.IsOfKind(CodeParameterKind.Path)));
+
+ foreach (var method in builderMethods)
+ {
+ if (method.ReturnType.IsCollection)
+ {
+ writer.WriteLine($"foreach (var cmd in builder.{method.Name}()) {{");
+ writer.IncreaseIndent();
+ writer.WriteLine($"command.AddCommand(cmd);");
+ writer.CloseBlock();
+ }
+ else
+ {
+ writer.WriteLine($"command.AddCommand(builder.{method.Name}());");
+ }
+ }
+ // SubCommands
+ }
+
+ writer.WriteLine("return command;");
+ }
+
+ private void WriteUnnamedBuildCommand(CodeMethod codeElement, LanguageWriter writer, CodeClass parent, IEnumerable classMethods)
+ {
+ if (codeElement.OriginalMethod?.MethodKind == CodeMethodKind.ClientConstructor)
+ {
+ var commandBuilderMethods = classMethods.Where(m => m.MethodKind == CodeMethodKind.CommandBuilder && m != codeElement).OrderBy(m => m.Name);
+ writer.WriteLine($"var command = new RootCommand();");
+ WriteCommandDescription(codeElement, writer);
+ foreach (var method in commandBuilderMethods)
+ {
+ writer.WriteLine($"command.AddCommand({method.Name}());");
+ }
+
+ writer.WriteLine("return command;");
+ }
+ else if (codeElement.OriginalIndexer != null)
+ {
+ var targetClass = conventions.GetTypeString(codeElement.OriginalIndexer.ReturnType, codeElement);
+ var builderMethods = (codeElement.OriginalIndexer.ReturnType as CodeType).TypeDefinition.GetChildElements(true).OfType()
+ .Where(m => m.IsOfKind(CodeMethodKind.CommandBuilder))
+ .OrderBy(m => m.Name);
+ conventions.AddRequestBuilderBody(parent, targetClass, writer, prefix: "var builder = ", pathParameters: codeElement.Parameters.Where(x => x.IsOfKind(CodeParameterKind.Path)));
+ writer.WriteLine("var commands = new List();");
+
+ foreach (var method in builderMethods)
+ {
+ if (method.ReturnType.IsCollection)
+ {
+ writer.WriteLine($"commands.AddRange(builder.{method.Name}());");
+ }
+ else
+ {
+ writer.WriteLine($"commands.Add(builder.{method.Name}());");
+ }
+ }
+
+ writer.WriteLine("return commands;");
+ }
+ }
+
+ protected virtual void WriteCommandHandlerBody(CodeMethod codeElement, RequestParams requestParams, bool isVoid, string returnType, LanguageWriter writer)
+ {
+ if (codeElement.HttpMethod == null) throw new InvalidOperationException("http method cannot be null");
+
+ var generatorMethod = (codeElement.Parent as CodeClass)
+ .Methods
+ .FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestGenerator) && x.HttpMethod == codeElement.HttpMethod);
+ var requestBodyParam = requestParams.requestBody;
+ if (requestBodyParam != null)
+ {
+ var requestBodyParamType = requestBodyParam?.Type as CodeType;
+ if (requestBodyParamType?.TypeDefinition is CodeClass)
+ {
+ writer.WriteLine($"using var stream = new MemoryStream(Encoding.UTF8.GetBytes({requestBodyParam.Name}));");
+ writer.WriteLine($"var parseNode = ParseNodeFactoryRegistry.DefaultInstance.GetRootParseNode(\"{generatorMethod.ContentType}\", stream);");
+
+ var typeString = conventions.GetTypeString(requestBodyParamType, requestBodyParam, false);
+
+ if (requestBodyParamType.IsCollection)
+ {
+ writer.WriteLine($"var model = parseNode.GetCollectionOfObjectValues<{typeString}>();");
+ }
+ else
+ {
+ writer.WriteLine($"var model = parseNode.GetObjectValue<{typeString}>();");
+ }
+
+ requestBodyParam.Name = "model";
+ }
+ else if (conventions.StreamTypeName.Equals(requestBodyParamType?.Name, StringComparison.OrdinalIgnoreCase))
+ {
+ var name = requestBodyParam.Name;
+ requestBodyParam.Name = "stream";
+ writer.WriteLine($"using var {requestBodyParam.Name} = {name}.OpenRead();");
+ }
+ }
+
+ var parametersList = new CodeParameter[] { requestParams.requestBody, requestParams.queryString, requestParams.headers, requestParams.options }
+ .Select(x => x?.Name).Where(x => x != null).DefaultIfEmpty().Aggregate((x, y) => $"{x}, {y}");
+ var separator = string.IsNullOrWhiteSpace(parametersList) ? "" : ", ";
+ WriteRequestInformation(writer, generatorMethod, parametersList, separator);
+
+ var errorMappingVarName = "default";
+ if (codeElement.ErrorMappings.Any())
+ {
+ errorMappingVarName = "errorMapping";
+ writer.WriteLine($"var {errorMappingVarName} = new Dictionary> {{");
+ writer.IncreaseIndent();
+ foreach (var errorMapping in codeElement.ErrorMappings)
+ {
+ writer.WriteLine($"{{\"{errorMapping.Key.ToUpperInvariant()}\", () => new {errorMapping.Value.Name.ToFirstCharacterUpperCase()}()}},");
+ }
+ writer.CloseBlock("};");
+ }
+
+ var requestMethod = "SendPrimitiveAsync";
+ if (isVoid)
+ {
+ requestMethod = "SendNoContentAsync";
+ }
+
+ writer.WriteLine($"{(isVoid ? string.Empty : "var response = ")}await RequestAdapter.{requestMethod}(requestInfo, errorMapping: {errorMappingVarName}, cancellationToken: {cancellationTokenParamName});");
+ }
+
+ private static void WriteRequestInformation(LanguageWriter writer, CodeMethod generatorMethod, string parametersList, string separator)
+ {
+ writer.WriteLine($"var requestInfo = {generatorMethod?.Name}({parametersList}{separator}q => {{");
+ if (generatorMethod?.PathAndQueryParameters != null)
+ {
+ writer.IncreaseIndent();
+ foreach (var param in generatorMethod.PathAndQueryParameters.Where(p => p.IsOfKind(CodeParameterKind.QueryParameter)))
+ {
+ var paramName = NormalizeToIdentifier(param.Name);
+ bool isStringParam = "string".Equals(param.Type.Name, StringComparison.OrdinalIgnoreCase) && !param.Type.IsCollection;
+ bool indentParam = true;
+ if (isStringParam)
+ {
+ writer.Write($"if (!String.IsNullOrEmpty({paramName})) ");
+ indentParam = false;
+ }
+
+ writer.Write($"q.{param.Name.ToFirstCharacterUpperCase()} = {paramName};", indentParam);
+
+ writer.WriteLine();
+ }
+ writer.CloseBlock("});");
+
+ foreach (var paramName in generatorMethod.PathAndQueryParameters.Where(p => p.IsOfKind(CodeParameterKind.PathParameters)).Select(p => p.Name))
+ {
+ writer.WriteLine($"requestInfo.PathParameters.Add(\"{paramName}\", {NormalizeToIdentifier(paramName)});");
+ }
+ }
+ else
+ {
+ writer.WriteLine("});");
+ }
+ }
+
+ ///
+ /// Converts delimited string into camel case for use as identifiers
+ ///
+ ///
+ ///
+ private static string NormalizeToIdentifier(string input)
+ {
+ return identifierRegex.Replace(input, m => m.Groups[1].Value.ToUpper());
+ }
+
+ ///
+ /// Converts camel-case or delimited string to '-' delimited string for use as a command option
+ ///
+ ///
+ ///
+ private static string NormalizeToOption(string input)
+ {
+ var result = camelCaseRegex.Replace(input, "-$1");
+ // 2 passes for cases like "singleValueLegacyExtendedProperty_id"
+ result = delimitedRegex.Replace(result, "-$1");
+
+ return result.ToLower();
+ }
+ }
+}
diff --git a/src/Kiota.Builder/Writers/Shell/ShellWriter.cs b/src/Kiota.Builder/Writers/Shell/ShellWriter.cs
new file mode 100644
index 0000000000..b1c3bc3812
--- /dev/null
+++ b/src/Kiota.Builder/Writers/Shell/ShellWriter.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Kiota.Builder.Writers.CSharp;
+
+namespace Kiota.Builder.Writers.Shell
+{
+ class ShellWriter : CSharpWriter
+ {
+ public ShellWriter(string rootPath, string clientNamespaceName) : base(rootPath, clientNamespaceName)
+ {
+ var conventionService = new CSharpConventionService();
+ AddOrReplaceCodeElementWriter(new CodeClassDeclarationWriter(conventionService));
+ AddOrReplaceCodeElementWriter(new CodeClassEndWriter(conventionService));
+ AddOrReplaceCodeElementWriter(new CodeEnumWriter(conventionService));
+ AddOrReplaceCodeElementWriter(new CodeIndexerWriter(conventionService));
+ AddOrReplaceCodeElementWriter(new ShellCodeMethodWriter(conventionService));
+ AddOrReplaceCodeElementWriter(new CodePropertyWriter(conventionService));
+ AddOrReplaceCodeElementWriter(new CodeTypeWriter(conventionService));
+ }
+ }
+}
diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs
index 269cfcf981..234259fa94 100644
--- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs
+++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs
@@ -6,12 +6,12 @@ public TypeScriptWriter(string rootPath, string clientNamespaceName)
{
PathSegmenter = new TypeScriptPathSegmenter(rootPath,clientNamespaceName);
var conventionService = new TypeScriptConventionService(null);
- AddCodeElementWriter(new CodeClassDeclarationWriter(conventionService, clientNamespaceName));
- AddCodeElementWriter(new CodeClassEndWriter());
- AddCodeElementWriter(new CodeEnumWriter(conventionService));
- AddCodeElementWriter(new CodeMethodWriter(conventionService));
- AddCodeElementWriter(new CodePropertyWriter(conventionService));
- AddCodeElementWriter(new CodeTypeWriter(conventionService));
+ AddOrReplaceCodeElementWriter(new CodeClassDeclarationWriter(conventionService, clientNamespaceName));
+ AddOrReplaceCodeElementWriter(new CodeClassEndWriter());
+ AddOrReplaceCodeElementWriter(new CodeEnumWriter(conventionService));
+ AddOrReplaceCodeElementWriter(new CodeMethodWriter(conventionService));
+ AddOrReplaceCodeElementWriter(new CodePropertyWriter(conventionService));
+ AddOrReplaceCodeElementWriter(new CodeTypeWriter(conventionService));
}
}
}
diff --git a/tests/Kiota.Builder.Tests/Refiners/ShellRefinerTests.cs b/tests/Kiota.Builder.Tests/Refiners/ShellRefinerTests.cs
new file mode 100644
index 0000000000..b9cfa4dead
--- /dev/null
+++ b/tests/Kiota.Builder.Tests/Refiners/ShellRefinerTests.cs
@@ -0,0 +1,84 @@
+using System;
+using System.Linq;
+using Xunit;
+
+namespace Kiota.Builder.Refiners.Tests;
+
+public class ShellRefinerTests {
+ private readonly CodeNamespace root = CodeNamespace.InitRootNamespace();
+
+ [Fact]
+ public void AddsUsingsForCommandTypesUsedInCommandBuilder() {
+ var requestBuilder = root.AddClass(new CodeClass {
+ Name = "somerequestbuilder",
+ ClassKind = CodeClassKind.RequestBuilder,
+ }).First();
+ var subNS = root.AddNamespace($"{root.Name}.subns"); // otherwise the import gets trimmed
+ var commandBuilder = requestBuilder.AddMethod(new CodeMethod {
+ Name = "GetCommand",
+ MethodKind = CodeMethodKind.CommandBuilder,
+ ReturnType = new CodeType {
+ Name = "Command",
+ IsExternal = true
+ }
+ }).First();
+ ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.Shell }, root);
+
+ var declaration = requestBuilder.StartBlock as CodeClass.Declaration;
+
+ Assert.Contains("System.CommandLine", declaration.Usings.Select(x => x.Declaration?.Name));
+ }
+
+ [Fact]
+ public void CreatesCommandBuilders() {
+ var requestBuilder = root.AddClass(new CodeClass {
+ Name = "somerequestbuilder",
+ ClassKind = CodeClassKind.RequestBuilder,
+ }).First();
+ var subNS = root.AddNamespace($"{root.Name}.subns"); // otherwise the import gets trimmed
+ // Add nav props
+ requestBuilder.AddProperty(new CodeProperty {
+ Name = "User",
+ PropertyKind = CodePropertyKind.RequestBuilder
+ });
+
+ // Add indexer
+ requestBuilder.SetIndexer(new CodeIndexer {
+ Name = "Users",
+ ReturnType = new CodeType {
+ Name = "Address"
+ }
+ });
+
+ // Add request executor
+ requestBuilder.AddMethod(new CodeMethod {
+ Name = "GetExecutor",
+ ReturnType = new CodeType {
+ Name = "User"
+ },
+ MethodKind = CodeMethodKind.RequestExecutor,
+ HttpMethod = HttpMethod.Get
+ });
+
+ // Add client constructor
+ requestBuilder.AddMethod(new CodeMethod {
+ Name = "constructor",
+ MethodKind = CodeMethodKind.ClientConstructor,
+ ReturnType = new CodeType {
+ Name = "void"
+ },
+ DeserializerModules = new() {"com.microsoft.kiota.serialization.Deserializer"},
+ SerializerModules = new() {"com.microsoft.kiota.serialization.Serializer"}
+ });
+
+ ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.Shell }, root);
+
+ var methods = root.GetChildElements().OfType().SelectMany(c => c.GetChildElements().OfType());
+ var methodNames = methods.Select(m => m.Name);
+
+ Assert.Contains("BuildCommand", methodNames);
+ Assert.Contains("BuildUserCommand", methodNames);
+ Assert.Contains("BuildListCommand", methodNames);
+ Assert.Contains("BuildRootCommand", methodNames);
+ }
+}
diff --git a/tests/Kiota.Builder.Tests/Writers/Shell/ShellCodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/Shell/ShellCodeMethodWriterTests.cs
new file mode 100644
index 0000000000..3c3d19b003
--- /dev/null
+++ b/tests/Kiota.Builder.Tests/Writers/Shell/ShellCodeMethodWriterTests.cs
@@ -0,0 +1,403 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Kiota.Builder.Writers;
+using Xunit;
+
+namespace Kiota.Builder.Tests.Writers.Shell;
+
+public class ShellCodeMethodWriterTests : IDisposable
+{
+ private const string DefaultPath = "./";
+ private const string DefaultName = "name";
+ private readonly StringWriter tw;
+ private readonly LanguageWriter writer;
+ private readonly CodeMethod method;
+ private readonly CodeClass parentClass;
+ private readonly CodeNamespace root;
+ private const string MethodName = "methodName";
+ private const string ReturnTypeName = "Somecustomtype";
+ private const string MethodDescription = "some description";
+ private const string ParamDescription = "some parameter description";
+ private const string ParamName = "paramName";
+
+ public ShellCodeMethodWriterTests()
+ {
+ writer = LanguageWriter.GetLanguageWriter(GenerationLanguage.Shell, DefaultPath, DefaultName);
+ tw = new StringWriter();
+ writer.SetTextWriter(tw);
+ root = CodeNamespace.InitRootNamespace();
+ parentClass = new CodeClass
+ {
+ Name = "parentClass"
+ };
+ root.AddClass(parentClass);
+ method = new CodeMethod
+ {
+ Name = MethodName
+ };
+ method.ReturnType = new CodeType
+ {
+ Name = ReturnTypeName
+ };
+ parentClass.AddMethod(method);
+ }
+
+ public void Dispose()
+ {
+ tw?.Dispose();
+ GC.SuppressFinalize(this);
+ }
+
+ private void AddRequestProperties() {
+ parentClass.AddProperty(new CodeProperty {
+ Name = "RequestAdapter",
+ PropertyKind = CodePropertyKind.RequestAdapter,
+ });
+ parentClass.AddProperty(new CodeProperty {
+ Name = "pathParameters",
+ PropertyKind = CodePropertyKind.PathParameters,
+ });
+ parentClass.AddProperty(new CodeProperty {
+ Name = "urlTemplate",
+ PropertyKind = CodePropertyKind.UrlTemplate,
+ });
+ }
+
+ private static void AddRequestBodyParameters(CodeMethod method) {
+ var stringType = new CodeType {
+ Name = "string",
+ };
+ method.AddParameter(new CodeParameter {
+ Name = "h",
+ ParameterKind = CodeParameterKind.Headers,
+ Type = stringType,
+ });
+ method.AddParameter(new CodeParameter{
+ Name = "q",
+ ParameterKind = CodeParameterKind.QueryParameter,
+ Type = stringType,
+ });
+ method.AddParameter(new CodeParameter{
+ Name = "b",
+ ParameterKind = CodeParameterKind.RequestBody,
+ Type = stringType,
+ });
+ method.AddParameter(new CodeParameter{
+ Name = "r",
+ ParameterKind = CodeParameterKind.ResponseHandler,
+ Type = stringType,
+ });
+ method.AddParameter(new CodeParameter {
+ Name = "o",
+ ParameterKind = CodeParameterKind.Options,
+ Type = stringType,
+ });
+ method.AddParameter(new CodeParameter
+ {
+ Name = "c",
+ ParameterKind = CodeParameterKind.Cancellation,
+ Type = stringType,
+ });
+ }
+
+ private static void AddPathAndQueryParameters(CodeMethod method) {
+ var stringType = new CodeType {
+ Name = "string",
+ };
+ method.AddPathOrQueryParameter(new CodeParameter{
+ Name = "q",
+ ParameterKind = CodeParameterKind.QueryParameter,
+ Type = stringType,
+ DefaultValue = "test",
+ Description = "The q option",
+ Optional = true
+ });
+ method.AddPathOrQueryParameter(new CodeParameter {
+ Name = "p",
+ ParameterKind = CodeParameterKind.Path,
+ Type = stringType
+ });
+ }
+
+ [Fact]
+ public void WritesRootCommand()
+ {
+ method.MethodKind = CodeMethodKind.CommandBuilder;
+ method.OriginalMethod = new CodeMethod
+ {
+ MethodKind = CodeMethodKind.ClientConstructor
+ };
+
+ writer.Write(method);
+
+ var result = tw.ToString();
+
+ Assert.Contains("var command = new RootCommand();", result);
+
+ Assert.Contains("return command;", result);
+ }
+
+ [Fact]
+ public void WritesRootCommandWithCommandBuilderMethods()
+ {
+ method.MethodKind = CodeMethodKind.CommandBuilder;
+ method.OriginalMethod = new CodeMethod
+ {
+ MethodKind = CodeMethodKind.ClientConstructor
+ };
+ parentClass.AddMethod(new CodeMethod {
+ Name = "BuildUserCommand",
+ MethodKind = CodeMethodKind.CommandBuilder
+ });
+
+ writer.Write(method);
+
+ var result = tw.ToString();
+
+ Assert.Contains("var command = new RootCommand();", result);
+ Assert.Contains("command.AddCommand(BuildUserCommand());", result);
+ Assert.Contains("return command;", result);
+ }
+
+ [Fact]
+ public void WritesIndexerCommands() {
+ method.MethodKind = CodeMethodKind.CommandBuilder;
+ var type = new CodeClass { Name = "TestClass", ClassKind = CodeClassKind.RequestBuilder };
+ type.AddMethod(new CodeMethod { MethodKind = CodeMethodKind.CommandBuilder, Name = "BuildTestMethod1", ReturnType = new CodeType() });
+ type.AddMethod(new CodeMethod { MethodKind = CodeMethodKind.CommandBuilder, Name = "BuildTestMethod2", ReturnType = new CodeType {CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Array} });
+ method.OriginalIndexer = new CodeIndexer {
+ ReturnType = new CodeType {
+ Name = "TestRequestBuilder",
+ TypeDefinition = type
+ }
+ };
+
+ AddRequestProperties();
+
+ writer.Write(method);
+ var result = tw.ToString();
+
+ Assert.Contains("var builder = new TestRequestBuilder", result);
+ Assert.Contains("var commands = new List();", result);
+ Assert.Contains("commands.Add(builder.BuildTestMethod1());", result);
+ Assert.Contains("commands.AddRange(builder.BuildTestMethod2());", result);
+ Assert.Contains("return commands;", result);
+ }
+
+ [Fact]
+ public void WritesContainerCommands() {
+ method.MethodKind = CodeMethodKind.CommandBuilder;
+ method.SimpleName = "User";
+ var type = new CodeClass { Name = "TestClass", ClassKind = CodeClassKind.RequestBuilder };
+ type.AddMethod(new CodeMethod { MethodKind = CodeMethodKind.CommandBuilder, Name = "BuildTestMethod1", ReturnType = new CodeType() });
+ type.AddMethod(new CodeMethod { MethodKind = CodeMethodKind.CommandBuilder, Name = "BuildTestMethod2", ReturnType = new CodeType {CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Array} });
+ type.Parent = new CodeType {
+ Name = "Test.Name"
+ };
+ method.AccessedProperty = new CodeProperty {
+ Type = new CodeType {
+ Name = "TestRequestBuilder",
+ TypeDefinition = type
+ }
+ };
+
+ AddRequestProperties();
+
+ writer.Write(method);
+ var result = tw.ToString();
+
+ Assert.Contains("var command = new Command(\"user\");", result);
+ Assert.Contains("var builder = new Test.Name.TestRequestBuilder", result);
+ Assert.Contains("command.AddCommand(builder.BuildTestMethod1());", result);
+ Assert.Contains("foreach (var cmd in builder.BuildTestMethod2()) {", result);
+ Assert.Contains("command.AddCommand(cmd);", result);
+ Assert.Contains("return command;", result);
+ }
+
+ [Fact]
+ public void WritesExecutableCommandForGetRequest() {
+
+ method.MethodKind = CodeMethodKind.CommandBuilder;
+ method.SimpleName = "User";
+ method.HttpMethod = HttpMethod.Get;
+ var stringType = new CodeType {
+ Name = "string",
+ };
+ var generatorMethod = new CodeMethod {
+ MethodKind = CodeMethodKind.RequestGenerator,
+ Name = "CreateGetRequestInformation",
+ HttpMethod = method.HttpMethod
+ };
+ method.OriginalMethod = new CodeMethod {
+ MethodKind = CodeMethodKind.RequestExecutor,
+ HttpMethod = method.HttpMethod,
+ ReturnType = stringType,
+ Parent = method.Parent
+ };
+ var codeClass = method.Parent as CodeClass;
+ codeClass.AddMethod(generatorMethod);
+
+ AddRequestProperties();
+ AddRequestBodyParameters(method.OriginalMethod);
+ AddPathAndQueryParameters(generatorMethod);
+
+ writer.Write(method);
+ var result = tw.ToString();
+
+ Assert.Contains("var command = new Command(\"user\");", result);
+ Assert.Contains("var qOption = new Option(\"-q\", getDefaultValue: ()=> \"test\", description: \"The q option\")", result);
+ Assert.Contains("qOption.IsRequired = false;", result);
+ Assert.Contains("command.AddOption(qOption);", result);
+ Assert.Contains("command.AddOption(outputOption);", result);
+ Assert.Contains("var requestInfo = CreateGetRequestInformation", result);
+ Assert.Contains("var response = await RequestAdapter.SendPrimitiveAsync(requestInfo, errorMapping: default, cancellationToken: cancellationToken);", result);
+ Assert.Contains("return command;", result);
+ }
+
+ [Fact]
+ public void WritesExecutableCommandForPostRequest() {
+
+ method.MethodKind = CodeMethodKind.CommandBuilder;
+ method.SimpleName = "User";
+ method.HttpMethod = HttpMethod.Post;
+ var stringType = new CodeType {
+ Name = "string",
+ };
+ var generatorMethod = new CodeMethod {
+ MethodKind = CodeMethodKind.RequestGenerator,
+ Name = "CreatePostRequestInformation",
+ HttpMethod = method.HttpMethod
+ };
+ method.OriginalMethod = new CodeMethod {
+ MethodKind = CodeMethodKind.RequestExecutor,
+ HttpMethod = method.HttpMethod,
+ ReturnType = stringType,
+ Parent = method.Parent
+ };
+ method.OriginalMethod.AddParameter(new CodeParameter{
+ Name = "body",
+ ParameterKind = CodeParameterKind.RequestBody,
+ Type = stringType,
+ });
+ var codeClass = method.Parent as CodeClass;
+ codeClass.AddMethod(generatorMethod);
+
+ AddRequestProperties();
+ AddPathAndQueryParameters(generatorMethod);
+
+ writer.Write(method);
+ var result = tw.ToString();
+
+ Assert.Contains("var command = new Command(\"user\");", result);
+ Assert.Contains("var qOption = new Option(\"-q\", getDefaultValue: ()=> \"test\", description: \"The q option\")", result);
+ Assert.Contains("qOption.IsRequired = false;", result);
+ Assert.Contains("command.AddOption(qOption);", result);
+ Assert.Contains("var bodyOption = new Option(\"--body\")", result);
+ Assert.Contains("bodyOption.IsRequired = true;", result);
+ Assert.Contains("command.AddOption(bodyOption);", result);
+ Assert.Contains("command.AddOption(outputOption);", result);
+ Assert.Contains("var requestInfo = CreatePostRequestInformation", result);
+ Assert.Contains("var response = await RequestAdapter.SendPrimitiveAsync(requestInfo, errorMapping: default, cancellationToken: cancellationToken);", result);
+ Assert.Contains("return command;", result);
+ }
+
+ [Fact]
+ public void WritesExecutableCommandForGetStreamRequest()
+ {
+
+ method.MethodKind = CodeMethodKind.CommandBuilder;
+ method.SimpleName = "User";
+ method.HttpMethod = HttpMethod.Get;
+ var streamType = new CodeType
+ {
+ Name = "stream",
+ };
+ var generatorMethod = new CodeMethod
+ {
+ MethodKind = CodeMethodKind.RequestGenerator,
+ Name = "CreateGetRequestInformation",
+ HttpMethod = method.HttpMethod
+ };
+ method.OriginalMethod = new CodeMethod
+ {
+ MethodKind = CodeMethodKind.RequestExecutor,
+ HttpMethod = method.HttpMethod,
+ ReturnType = streamType,
+ Parent = method.Parent
+ };
+ var codeClass = method.Parent as CodeClass;
+ codeClass.AddMethod(generatorMethod);
+
+ AddRequestProperties();
+ AddRequestBodyParameters(method.OriginalMethod);
+ AddPathAndQueryParameters(generatorMethod);
+
+ writer.Write(method);
+ var result = tw.ToString();
+
+ Assert.Contains("var command = new Command(\"user\");", result);
+ Assert.Contains("var qOption = new Option(\"-q\", getDefaultValue: ()=> \"test\", description: \"The q option\")", result);
+ Assert.Contains("qOption.IsRequired = false;", result);
+ Assert.Contains("command.AddOption(qOption);", result);
+ Assert.Contains("command.AddOption(outputOption);", result);
+ Assert.Contains("var fileOption = new Option(\"--file\");", result);
+ Assert.Contains("command.AddOption(fileOption);", result);
+ Assert.Contains("var requestInfo = CreateGetRequestInformation", result);
+ Assert.Contains("var response = await RequestAdapter.SendPrimitiveAsync(requestInfo, errorMapping: default, cancellationToken: cancellationToken);", result);
+ Assert.Contains("return command;", result);
+ }
+
+ [Fact]
+ public void WritesExecutableCommandForPostVoidRequest() {
+
+ method.MethodKind = CodeMethodKind.CommandBuilder;
+ method.SimpleName = "User";
+ method.HttpMethod = HttpMethod.Post;
+ var stringType = new CodeType {
+ Name = "string",
+ };
+ var voidType = new CodeType {
+ Name = "void",
+ };
+ var generatorMethod = new CodeMethod {
+ MethodKind = CodeMethodKind.RequestGenerator,
+ Name = "CreatePostRequestInformation",
+ HttpMethod = method.HttpMethod
+ };
+ method.OriginalMethod = new CodeMethod {
+ MethodKind = CodeMethodKind.RequestExecutor,
+ HttpMethod = method.HttpMethod,
+ ReturnType = voidType,
+ Parent = method.Parent
+ };
+ method.OriginalMethod.AddParameter(new CodeParameter{
+ Name = "body",
+ ParameterKind = CodeParameterKind.RequestBody,
+ Type = stringType,
+ });
+ var codeClass = method.Parent as CodeClass;
+ codeClass.AddMethod(generatorMethod);
+
+ AddRequestProperties();
+ AddPathAndQueryParameters(generatorMethod);
+
+ writer.Write(method);
+ var result = tw.ToString();
+
+ Assert.Contains("var command = new Command(\"user\");", result);
+ Assert.Contains("var qOption = new Option(\"-q\", getDefaultValue: ()=> \"test\", description: \"The q option\")", result);
+ Assert.Contains("qOption.IsRequired = false;", result);
+ Assert.Contains("command.AddOption(qOption);", result);
+ Assert.Contains("var bodyOption = new Option(\"--body\")", result);
+ Assert.Contains("bodyOption.IsRequired = true;", result);
+ Assert.Contains("command.AddOption(bodyOption);", result);
+ Assert.Contains("var requestInfo = CreatePostRequestInformation", result);
+ Assert.Contains("await RequestAdapter.SendNoContentAsync(requestInfo, errorMapping: default, cancellationToken: cancellationToken);", result);
+ Assert.Contains("console.WriteLine(\"Success\");", result);
+ Assert.Contains("return command;", result);
+ }
+}