Skip to content

Commit

Permalink
Add Ballerina language support
Browse files Browse the repository at this point in the history
  • Loading branch information
xlight05 committed Mar 5, 2023
1 parent a726dec commit 2f39044
Show file tree
Hide file tree
Showing 27 changed files with 495 additions and 2 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,7 @@
"PowerShell",
"Python",
"TypeScript",
"Ballerina",
"Custom"
],
"description": "%azureFunctions.projectLanguage%",
Expand All @@ -867,6 +868,7 @@
"",
"",
"",
"",
""
]
},
Expand Down
1 change: 1 addition & 0 deletions resources/backupTemplates/ballerina/bindings/bindings.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"en":{"temp_category_api":"API & Webhooks","temp_category_core":"Core","temp_category_dataProcessing":"Data Processing","HttpTrigger_description":"A function that will be run whenever it receives an HTTP request, responding based on data in the body or query string","BlobTrigger_description":"A function that will be run whenever a blob is added to a specified container","CosmosDBTrigger_description":"A function that will be run whenever documents change in a document collection","QueueTrigger_description":"A function that will be run whenever a message is added to a specified Azure Storage queue","TimerTrigger_description":"A function that will be run on a specified schedule","variables_storageConnStringLabel":"Storage account connection","variables_appSettingsHelp":"The name of the app setting containing your storage account connection string.","variables_selectConnection":"Click select to choose a connection","variables_parameterName":"The parameter name must be an alphanumeric string of any number of characters and cannot start with a number.","timerTrigger_displayName":"Timer","timerTrigger_name_help":"The name used to identify this trigger in your code","timerTrigger_name_label":"Timestamp parameter name","timerTrigger_schedule_help":"Enter a cron expression of the format '{second} {minute} {hour} {day} {month} {day of week}' to specify the schedule.","timerTrigger_schedule_label":"Schedule","queueTrigger_displayName":"Azure Queue Storage","queueTrigger_queueName_help":"Name of the queue from which the message will be read","queueTrigger_name_label":"Message parameter name","queueTrigger_queueName_errorText":"Queue name must start and end with a letter or number, and it can contain only lowercase letters, numbers, and the hyphen. The name must be 3 to 63 characters.","queueTrigger_name_help":"The name used to identify this trigger in your code","queueTrigger_queueName_label":"Queue name","blobTrigger_displayName":"Azure Blob Storage","blobTrigger_name_label":"Blob parameter name","blobTrigger_name_help":"The name used to identify this trigger in your code","blobTrigger_path_label":"Path","blobTrigger_path_help":"The path within your storage account that the trigger will monitor.","blobTrigger_path_errorText":"Your blob path must be a container name of 3 to 63 characters, followed by a blob name of 1 to 1,024 characters. The blob name can be any combination of characters and can contain a maximum of 254 path segments, which are separated by the forward slash (/).","httpTrigger_displayName":"HTTP","httpTrigger_name_label":"Request parameter name","httpTrigger_name_help":"The name used to identify this trigger in your code","httpTrigger_authLevel_label":"Authorization level","httpTrigger_authLevel_help":"Authorization level controls whether the function requires an API key and which key to use; Function uses a function key; Admin uses your master key. The function and master keys are found in the 'keys' management panel on the portal, when your function is selected. For user-based authentication, go to Function App Settings.","httpTrigger_methods_label":"Selected HTTP methods","httpTrigger_methods_help":"These are the only methods to which this function will respond.","httpTrigger_methodRule_label":"Allowed HTTP methods","httpTrigger_route_label":"Route template","httpTrigger_route_help":"The route template setting allows you to change the URI that triggers this function. The values should be a relative path. Path segments may be treated as parameters by surrounding them with curly braces. For example: customer/{customerId}","httpTrigger_methodRule_help":"HttpTrigger can respond to any HTTP method. If you wish to restrict support to specific methods, choose the 'Selected methods' option.","httpTrigger_mode_label":"Mode","httpTrigger_mode_help":"The mode of the trigger. \"Standard\" means that the request will be standard HTTP with no additional semantics. \"Webhook\" means that the request will be processed according to a specified webhook type.","cosmosDB_trigger_displayName":"Azure Cosmos DB","cosmosDBIn_collectionName_help":"Name of the collection to be monitored.","cosmosDBIn_collectionName_label":"Collection name","cosmosDBIn_connection_help":"The name of the App Setting containing the connection string to the service that contains the collection to be monitored.","cosmosDBIn_connection_label":"Cosmos DB account connection","cosmosDBIn_databaseName_help":"Name of the Cosmos DB database that includes the collection to be monitored.","cosmosDBIn_databaseName_label":"Database name","cosmosDBIn_leaseCollectionName_help":"Name of the collection to store the leases.","cosmosDBIn_leaseCollectionName_label":"Collection name for leases","cosmosDBIn_leaseDatabaseName_help":"Name of the database that includes the collection to store the leases.","cosmosDBIn_leaseDatabaseName_label":"Database name for leases","cosmosDBIn_name_help":"The name used to identify this binding in your code","cosmosDBIn_name_label":"Document collection parameter name","cosmosDBIn_createIfNotExists_help":"Checks for existence and automatically creates the leases collection.","cosmosDBIn_createIfNotExists_label":"Create lease collection if it does not exist"}}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"id":"HttpTrigger-Ballerina","runtime":"2","files":{"%functionName%.bal":"import ballerinax/azure_functions as af;\n\n// This function gets triggered by an HTTP call with the name query parameter and returns a processed HTTP output to the caller.\n@af:HttpTrigger{\n authLevel: \"%authLevel%\"\n}\nlistener af:HttpListener httpListener = new af:HttpListener();\nservice /%functionName% on httpListener {\n resource function get .(string name) returns string {\n return \"Hello, \" + name + \"!\";\n }\n}\n"},"metadata":{"defaultFunctionName":"httpTrigger","description":"$HttpTrigger_description","name":"HTTP trigger","language":"Ballerina","triggerType":"httpTrigger","category":["$temp_category_core","$temp_category_api"],"categoryStyle":"http","enabledInTryMode":true,"userPrompt":["authLevel"]}},{"id":"BlobTrigger-Ballerina","runtime":"2","files":{"%functionName%.bal":"import ballerinax/azure_functions as af;\nimport ballerina/log;\n\n// The following Function will be invoked when a new blob added to the specified blob storage.\n@af:BlobTrigger {\n path: \"%path%\",\n connection: \"%connection%\"\n}\nlistener af:BlobListener blobListener = new af:BlobListener();\n\nservice \"%functionName%\" on blobListener {\n remote function onUpdate(byte[] blobIn) {\n log:printInfo(\"Blob Store updated with file of \" + blobIn.length().toString() + \" bytes\");\n }\n}\n"},"metadata":{"defaultFunctionName":"blobTrigger","description":"$BlobTrigger_description","name":"Blob trigger","language":"Ballerina","triggerType":"blobTrigger","category":["$temp_category_core","$temp_category_dataProcessing"],"categoryStyle":"blob","enabledInTryMode":true,"userPrompt":["connection","path"]}},{"id":"CosmosDBTrigger-Ballerina","runtime":"2","files":{"%functionName%.bal":"import ballerina/log;\nimport ballerinax/azure_functions as af;\n\n// The following Function will be invoked when an entry is added to CosmosDB collection.\n@af:CosmosDBTrigger {connectionStringSetting: \"%connectionStringSetting%\", databaseName: \"%databaseName%\", collectionName: \"%collectionName%\"}\nlistener af:CosmosDBListener cosmosEp = new ();\n\ntype Users record {\n string id;\n string name;\n};\n\nservice \"%functionName%\" on cosmosEp {\n remote function onUpdate(Users[] users) {\n log:printInfo(users.toJsonString());\n }\n}\n"},"metadata":{"defaultFunctionName":"cosmosTrigger","description":"$CosmosDBTrigger_description","name":"CosmosDB trigger","language":"Ballerina","triggerType":"cosmosDBTrigger","category":["$temp_category_core","$temp_category_dataProcessing"],"categoryStyle":"cosmosDB","enabledInTryMode":true,"userPrompt":["connectionStringSetting","databaseName","collectionName"]}},{"id":"QueueTrigger-Ballerina","runtime":"2","files":{"%functionName%.bal":"import ballerina/log;\nimport ballerinax/azure_functions as af;\n\n\n// The following Function will be executed when a message is added to the queue storage.\n@af:QueueTrigger {\n queueName: \"%queueName%\",\n connection: \"%connection%\"\n}\nlistener af:QueueListener queueListener = new af:QueueListener();\n\nservice \"%functionName%\" on queueListener {\n remote function onMessage(string message) {\n log:printInfo(\"Queue message received: \" + message);\n }\n}\n"},"metadata":{"defaultFunctionName":"queueTrigger","description":"$QueueTrigger_description","name":"Queue trigger","language":"Ballerina","triggerType":"queueTrigger","category":["$temp_category_core","$temp_category_dataProcessing"],"categoryStyle":"queue","enabledInTryMode":true,"userPrompt":["connection","queueName"]}},{"id":"TimerTrigger-Ballerina","runtime":"2","files":{"%functionName%.bal":"import ballerina/time;\nimport ballerina/log;\nimport ballerinax/azure_functions as af;\n\n// The following function will be invoked periodically according to the schedule given.\n@af:TimerTrigger {schedule: \"%schedule%\"}\nlistener af:TimerListener timerEp = new ();\n\nservice \"%functionName%\" on timerEp {\n remote function onTrigger(af:TimerMetadata metadata) {\n log:printInfo(\"Function Executed at \" + time:utcToString(time:utcNow()));\n }\n}\n"},"metadata":{"defaultFunctionName":"timerTrigger","description":"$TimerTrigger_description","name":"Timer trigger","language":"Ballerina","triggerType":"timerTrigger","category":["$temp_category_core","$temp_category_dataProcessing"],"categoryStyle":"timer","enabledInTryMode":true,"userPrompt":["schedule"]}}]
2 changes: 2 additions & 0 deletions src/LocalResourceProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ async function getCompiledProjectInfo(context: IActionContext, projectPath: stri
} else {
return { compiledProjectPath: path.join(projectPath, getJavaDebugSubpath(functionAppName, buildTool)), isIsolated: false };
}
} else if (projectLanguage === ProjectLanguage.Ballerina) {
return { compiledProjectPath: projectPath, isIsolated: false };
} else {
return undefined;
}
Expand Down
8 changes: 8 additions & 0 deletions src/commands/createFunction/FunctionSubWizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { IFunctionTemplate } from '../../templates/IFunctionTemplate';
import { isNodeV4Plus, isPythonV2Plus } from '../../utils/programmingModelUtils';
import { addBindingSettingSteps } from '../addBinding/settingSteps/addBindingSettingSteps';
import { JavaPackageNameStep } from '../createNewProject/javaSteps/JavaPackageNameStep';
import { BallerinaFunctionCreateStep } from './ballerinaSteps/BallerinaFunctionCreateStep';
import { BallerinaFunctionNameStep } from './ballerinaSteps/BallerinaFunctionNameStep';
import { DotnetFunctionCreateStep } from './dotnetSteps/DotnetFunctionCreateStep';
import { DotnetFunctionNameStep } from './dotnetSteps/DotnetFunctionNameStep';
import { DotnetNamespaceStep } from './dotnetSteps/DotnetNamespaceStep';
Expand Down Expand Up @@ -45,6 +47,9 @@ export class FunctionSubWizard {
case ProjectLanguage.Java:
promptSteps.push(new JavaPackageNameStep(), new JavaFunctionNameStep());
break;
case ProjectLanguage.Ballerina:
promptSteps.push(new BallerinaFunctionNameStep());
break;
case ProjectLanguage.CSharp:
case ProjectLanguage.FSharp:
promptSteps.push(new DotnetFunctionNameStep(), new DotnetNamespaceStep());
Expand Down Expand Up @@ -81,6 +86,9 @@ export class FunctionSubWizard {
case ProjectLanguage.TypeScript:
executeSteps.push(new TypeScriptFunctionCreateStep());
break;
case ProjectLanguage.Ballerina:
executeSteps.push(new BallerinaFunctionCreateStep());
break;
default:
if (isV2PythonModel) {
executeSteps.push(new PythonFunctionCreateStep());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { AzExtFsExtra, nonNullProp } from '@microsoft/vscode-azext-utils';
import * as path from 'path';
import { FunctionCreateStepBase } from '../FunctionCreateStepBase';
import { IBallerinaFunctionTemplate, IBallerinaFunctionWizardContext } from './IBallerinaFunctionWizardContext';

export class BallerinaFunctionCreateStep extends FunctionCreateStepBase<IBallerinaFunctionWizardContext> {
public async executeCore(context: IBallerinaFunctionWizardContext): Promise<string> {
const functionPath = context.projectPath;
await AzExtFsExtra.ensureDir(functionPath);

const functionName = nonNullProp(context, 'functionName');
const fileName = `${functionName}.bal`;

const template: IBallerinaFunctionTemplate = nonNullProp(context, 'functionTemplate');
await Promise.all(Object.keys(template.templateFiles).map(async f => {
let contents = template.templateFiles[f];
contents = contents.replace(/%functionName%/g, functionName);

for (const setting of template.userPromptedSettings) {
// the setting name keys are lowercased
contents = contents.replace(new RegExp(`%${setting.name}%`, 'g'), context[setting.name.toLowerCase()]);
}

await AzExtFsExtra.writeFile(path.join(functionPath, fileName), contents);
}));

return path.join(functionPath, fileName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { AzExtFsExtra } from '@microsoft/vscode-azext-utils';
import { localize } from "../../../localize";
import { IFunctionTemplate } from '../../../templates/IFunctionTemplate';
import { nonNullProp } from '../../../utils/nonNull';
import { getBallerinaFunctionFilePath, getBallerinaPackagePath, IBallerinaProjectWizardContext } from '../../createNewProject/ballerinaSteps/IBallerinaProjectWizardContext';
import { FunctionNameStepBase } from '../FunctionNameStepBase';
import { IFunctionWizardContext } from '../IFunctionWizardContext';

export class BallerinaFunctionNameStep extends FunctionNameStepBase<IFunctionWizardContext & IBallerinaProjectWizardContext> {
protected async getUniqueFunctionName(context: IFunctionWizardContext & IBallerinaProjectWizardContext): Promise<string | undefined> {
const template: IFunctionTemplate = nonNullProp(context, 'functionTemplate');
return await this.getUniqueFsPath(getBallerinaPackagePath(context.projectPath), template.defaultFunctionName, '.bal');
}

protected async validateFunctionNameCore(context: IFunctionWizardContext & IBallerinaProjectWizardContext, name: string): Promise<string | undefined> {
if (await AzExtFsExtra.pathExists(getBallerinaFunctionFilePath(context.projectPath, name))) {
return localize('existingError', 'A function with name "{0}" already exists in package "{1}".', name);
} else {
return undefined;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { IFunctionTemplate } from '../../../templates/IFunctionTemplate';
import { IFunctionWizardContext } from '../IFunctionWizardContext';

export interface IBallerinaFunctionWizardContext extends IFunctionWizardContext {
functionTemplate?: IBallerinaFunctionTemplate;
}

export interface IBallerinaFunctionTemplate extends IFunctionTemplate {
templateFiles: { [filename: string]: string };
}
5 changes: 5 additions & 0 deletions src/commands/createNewProject/NewProjectLanguageStep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { addInitVSCodeSteps } from '../initProjectForVSCode/InitVSCodeLanguageSt
import { DotnetRuntimeStep } from './dotnetSteps/DotnetRuntimeStep';
import { IProjectWizardContext } from './IProjectWizardContext';
import { addJavaCreateProjectSteps } from './javaSteps/addJavaCreateProjectSteps';
import { addBallerinaCreateProjectSteps } from './ballerinaSteps/addBallerinaCreateProjectSteps';
import { ProgrammingModelStep } from './ProgrammingModelStep';
import { CustomProjectCreateStep } from './ProjectCreateStep/CustomProjectCreateStep';
import { DotnetProjectCreateStep } from './ProjectCreateStep/DotnetProjectCreateStep';
Expand Down Expand Up @@ -48,6 +49,7 @@ export class NewProjectLanguageStep extends AzureWizardPromptStep<IProjectWizard
{ label: pythonNewModelPreview, data: { language: ProjectLanguage.Python, model: previewPythonModel } },
{ label: ProjectLanguage.Java, data: { language: ProjectLanguage.Java } },
{ label: ProjectLanguage.PowerShell, data: { language: ProjectLanguage.PowerShell } },
{ label: ProjectLanguage.Ballerina, data: { language: ProjectLanguage.Ballerina } },
{ label: localize('customHandler', 'Custom Handler'), data: { language: ProjectLanguage.Custom } }
];

Expand Down Expand Up @@ -111,6 +113,9 @@ export class NewProjectLanguageStep extends AzureWizardPromptStep<IProjectWizard
case ProjectLanguage.Java:
await addJavaCreateProjectSteps(context, promptSteps, executeSteps);
break;
case ProjectLanguage.Ballerina:
await addBallerinaCreateProjectSteps(promptSteps, executeSteps);
break;
case ProjectLanguage.Custom:
executeSteps.push(new CustomProjectCreateStep());
break;
Expand Down
Loading

0 comments on commit 2f39044

Please sign in to comment.