Skip to content

Commit

Permalink
Merge pull request #738 from microsoft/feat/cli_generator
Browse files Browse the repository at this point in the history
Add shell language support
  • Loading branch information
baywet authored Feb 16, 2022
2 parents da6c79a + d5d3c02 commit 980a070
Show file tree
Hide file tree
Showing 33 changed files with 1,546 additions and 135 deletions.
7 changes: 7 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
64 changes: 64 additions & 0 deletions .github/workflows/cli-commons.yml
Original file line number Diff line number Diff line change
@@ -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 }}
4 changes: 2 additions & 2 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/sonarcloud.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
34 changes: 34 additions & 0 deletions cli/commons/Microsoft.Kiota.Cli.Commons.sln
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions cli/commons/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Kiota CLI Commons Package

Contains CLI specific types that are referenced in code generated by the shell language.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Microsoft.Kiota.Cli.Commons.IO;

public enum FormatterType
{
JSON,
TABLE,
NONE
}
10 changes: 10 additions & 0 deletions cli/commons/src/Microsoft.Kiota.Cli.Commons/IO/IOutputFormatter.cs
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Microsoft.Kiota.Cli.Commons.IO;

public interface IOutputFormatterFactory
{
IOutputFormatter GetFormatter(FormatterType formatterType);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>0.1.0</Version>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="System.CommandLine" Version="2.0.0-beta2.21617.1" />
</ItemGroup>

</Project>
39 changes: 37 additions & 2 deletions src/Kiota.Builder/CodeDOM/CodeMethod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public enum CodeMethodKind
RequestBuilderWithParameters,
RawUrlConstructor,
NullCheck,
CommandBuilder,
}
public enum HttpMethod {
Get,
Expand Down Expand Up @@ -48,14 +49,40 @@ 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<CodeParameter> Parameters { get => parameters; }
public bool IsStatic {get;set;} = false;
public bool IsAsync {get;set;} = true;
public string Description {get; set;}
/// <summary>
/// The property this method accesses to when it's a getter or setter.
/// </summary>
public CodeProperty AccessedProperty { get; set; }
public CodeProperty AccessedProperty { get; set;
}
/// <summary>
/// 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)
/// </summary>
public IEnumerable<CodeParameter> 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<CodeParameter>(parameters);
else if (PathAndQueryParameters is List<CodeParameter> cast)
cast.AddRange(parameters);
}
public bool IsOfKind(params CodeMethodKind[] kinds) {
return kinds?.Contains(MethodKind) ?? false;
}
Expand Down Expand Up @@ -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
/// </summary>
public string BaseUrl { get; set; }
public string BaseUrl { get; set;
}

/// <summary>
/// This is currently used for CommandBuilder methods to get the original name without the Build prefix & Command suffix.
/// Avoids regex operations
/// </summary>
public string SimpleName { get; set; } = String.Empty;

/// <summary>
/// Mapping of the error code and response types for this method.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Linq;
using System.Linq;
using System.Collections.Generic;

namespace Kiota.Builder {
Expand Down
6 changes: 5 additions & 1 deletion src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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('-', '_');
}
}
}
6 changes: 3 additions & 3 deletions src/Kiota.Builder/Extensions/StringExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down
3 changes: 2 additions & 1 deletion src/Kiota.Builder/GenerationLanguage.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Kiota.Builder {
namespace Kiota.Builder {
public enum GenerationLanguage {
CSharp,
Java,
Expand All @@ -7,5 +7,6 @@ public enum GenerationLanguage {
Python,
Go,
Ruby,
Shell
}
}
37 changes: 37 additions & 0 deletions src/Kiota.Builder/KiotaBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -982,6 +1012,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)
{
Expand Down
Loading

0 comments on commit 980a070

Please sign in to comment.