powershell/internal/name-inferrer.ts (314 lines of code) (raw):
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { pascalCase, EnglishPluralizationService } from '@azure-tools/codegen';
import { Channel, Message } from '@autorest/extension-base';
import { length } from '@azure-tools/linq';
function getPluralizationService(): EnglishPluralizationService {
const result = new EnglishPluralizationService();
result.addWord('Database', 'Databases');
result.addWord('database', 'databases');
result.addWord('Premise', 'Premises');
result.addWord('premise', 'premises');
return result;
}
export function singularize(word: string): string {
return getPluralizationService().singularize(word);
}
function getSingularizedValue(name: string): string {
return pascalCase([singularize(name)]);
}
const cmdVerbMapGetVerb: { [verb: string]: string | Array<string> } = {
'Add': 'Common',
'Clear': 'Common',
'Close': 'Common',
'Copy': 'Common',
'Enter': 'Common',
'Exit': 'Common',
'Find': 'Common',
'Format': 'Common',
'Get': 'Common',
'Hide': 'Common',
'Join': 'Common',
'Lock': 'Common',
'Move': 'Common',
'New': 'Common',
'Open': 'Common',
'Optimize': 'Common',
'Pop': 'Common',
'Push': 'Common',
'Redo': 'Common',
'Remove': 'Common',
'Rename': 'Common',
'Reset': 'Common',
'Resize': 'Common',
'Search': 'Common',
'Select': 'Common',
'Set': 'Common',
'Show': 'Common',
'Skip': 'Common',
'Split': 'Common',
'Step': 'Common',
'Switch': 'Common',
'Undo': 'Common',
'Unlock': 'Common',
'Watch': 'Common',
'Backup': 'Data',
'Checkpoint': 'Data',
'Compare': 'Data',
'Compress': 'Data',
'Convert': 'Data',
'ConvertFrom': 'Data',
'ConvertTo': 'Data',
'Dismount': 'Data',
'Edit': 'Data',
'Expand': 'Data',
'Export': 'Data',
'Group': 'Data',
'Import': 'Data',
'Initialize': 'Data',
'Limit': 'Data',
'Merge': 'Data',
'Mount': 'Data',
'Out': 'Data',
'Publish': 'Data',
'Restore': 'Data',
'Save': 'Data',
'Sync': 'Data',
'Unpublish': 'Data',
'Update': 'Data',
'Approve': 'Lifecycle',
'Assert': 'Lifecycle',
'Complete': 'Lifecycle',
'Confirm': 'Lifecycle',
'Deny': 'Lifecycle',
'Disable': 'Lifecycle',
'Enable': 'Lifecycle',
'Install': 'Lifecycle',
'Invoke': 'Lifecycle',
'Register': 'Lifecycle',
'Request': 'Lifecycle',
'Restart': 'Lifecycle',
'Resume': 'Lifecycle',
'Start': 'Lifecycle',
'Stop': 'Lifecycle',
'Submit': 'Lifecycle',
'Suspend': 'Lifecycle',
'Uninstall': 'Lifecycle',
'Unregister': 'Lifecycle',
'Wait': 'Lifecycle',
'Debug': 'Diagnostic',
'Measure': 'Diagnostic',
'Ping': 'Diagnostic',
'Repair': 'Diagnostic',
'Resolve': 'Diagnostic',
'Test': 'Diagnostic',
'Trace': 'Diagnostic',
'Connect': 'Communications',
'Disconnect': 'Communications',
'Read': 'Communications',
'Receive': 'Communications',
'Send': 'Communications',
'Write': 'Communications',
'Block': 'Security',
'Grant': 'Security',
'Protect': 'Security',
'Revoke': 'Security',
'Unblock': 'Security',
'Unprotect': 'Security',
'Use': 'Other',
};
const cmdVerbMapCustom: { [verb: string]: string | Array<string> } = {
'Access': 'Get',
'List': 'Get',
'Cat': 'Get',
'Type': 'Get',
'Dir': 'Get',
'Obtain': 'Get',
'Dump': 'Get',
'Acquire': 'Get',
'Examine': 'Get',
'Suggest': 'Get',
'Retrieve': 'Get',
'Create': 'New',
'Generate': 'New',
'Allocate': 'New',
'Provision': 'New',
'Make': 'New',
'Regenerate': 'New', // Alternatives: Redo, Update, Reset
'Replace': 'Update',
'Failover': 'Set',
'Assign': 'Set',
'Configure': 'Set',
'Activate': 'Initialize',
'Build': 'Build',
'Compile': 'Build',
'Deploy': 'Deploy',
'Apply': 'Add',
'Append': 'Add',
'Attach': 'Add',
'Concatenate': 'Add',
'Insert': 'Add',
'Delete': 'Remove',
'Cut': 'Remove',
'Dispose': 'Remove',
'Discard': 'Remove',
'Generalize': 'Reset',
'Patch': 'Update',
'Refresh': 'Update',
'Reprocess': 'Update', // Alternatives: Redo
'Upgrade': 'Update',
'Reimage': 'Update', // Alternatives: Format, Reset
'Retarget': 'Update',
'Validate': 'Test',
'Check': 'Test',
'Verify': 'Test',
'Analyze': 'Test',
'Is': 'Test',
'Evaluate': 'Test', // Alternatives: Invoke
'Power': 'Start',
'PowerOn': 'Start',
'Run': 'Start', // Alternatives: Invoke
'Trigger': 'Start',
'Pause': 'Suspend',
'Cancel': 'Stop',
'PowerOff': 'Stop',
'End': 'Stop',
'Shutdown': 'Stop',
'Reboot': 'Restart',
'ForceReboot': 'Restart',
'Finish': 'Complete',
'Wipe': 'Clear',
'Purge': 'Clear', // Alternatives: Remove
'Flush': 'Clear',
'Erase': 'Clear',
'Unmark': 'Clear',
'Unset': 'Clear',
'Nullify': 'Clear',
'Recover': 'Restore',
'Undelete': 'Restore',
'Synchronize': 'Sync',
'Synch': 'Sync',
'Load': 'Import',
'Capture': 'Export', // Alternatives: Trace
'Migrate': 'Move', // Alternatives: Export
'Transfer': 'Move',
'Name': 'Move',
'Reassociate': 'Move',
'Change': 'Rename',
'Swap': 'Switch', // Alternatives: Move
'Execute': 'Invoke',
'Perform': 'Invoke',
'Discover': 'Find', // Alternatives: Search
'Locate': 'Find',
'Release': 'Publish', // Alternatives: Clear, Unlock
'Resubmit': 'Submit',
'Duplicate': 'Copy',
'Clone': 'Copy',
'Replicate': 'Copy',
'Into': 'Enter',
'Combine': 'Join',
'Unite': 'Join',
'Associate': 'Join',
'Restrict': 'Lock',
'Secure': 'Lock',
'Unrestrict': 'Unlock',
'Unsecure': 'Unlock',
'Display': 'Show',
'Produce': 'Show',
'Bypass': 'Skip',
'Jump': 'Skip',
'Separate': 'Split',
'Notify': 'Send',
'Authorize': 'Grant'
};
const cmdVerbMap = { ...cmdVerbMapGetVerb, ...cmdVerbMapCustom };
function mapVerb(verb: string): Array<string> {
verb = verb.toLowerCase();
const keyHits = Object.keys(cmdVerbMap).filter(key => key.toLowerCase() === verb);
if (length(keyHits) === 0) { return []; }
let value = cmdVerbMap[keyHits[0]];
if (!Array.isArray(value)) { value = [value]; }
return value;
}
function existsVerb(verb: string) {
return mapVerb(verb).length > 0;
}
export function getCommandName(operationId: string, onMessage: (message: Message) => void): Array<{ noun?: string; verb: string; variant: string }> {
const opIdValues = operationId.split('_', 2);
// OperationId can be specified without '_' (Underscore), Verb will retrieved by the below logic for non-approved verbs.
let cmdNoun = length(opIdValues) === 2 ? getSingularizedValue(opIdValues[0]) : '';
let cmdVerb = length(opIdValues) === 2 ? opIdValues[1] : getSingularizedValue(operationId);
let cmdVerbs: Array<string> = [cmdVerb];
const variant = operationId;
if (!Object
.keys(cmdVerbMapGetVerb)
.map(v => v.toLowerCase())
.includes(cmdVerb.toLowerCase())) {
const unapprovedVerb = cmdVerb;
onMessage({ Channel: Channel.Information, Text: `Operation '${operationId}': Verb '${unapprovedVerb}' not an approved verb.` });
if (Object
.keys(cmdVerbMapCustom)
.map(v => v.toLowerCase())
.includes(cmdVerb.toLowerCase())) {
// This condition happens when there aren't any suffixes
cmdVerbs = mapVerb(cmdVerb);
for (const v of cmdVerbs) {
onMessage({ Channel: Channel.Information, Text: `Operation '${operationId}': Using verb '${v}' in place of '${unapprovedVerb}'.` });
}
} else {
// This condition happens in cases like: CreateSuffix, CreateOrUpdateSuffix
let longestVerbMatch: string | null = null;
let currentVerbCandidate = '';
let firstWord = '';
let firstWordStarted = false;
let buildFirstWord = false;
let firstWordEnd = -1;
let verbMatchEnd = -1;
for (let i = 0; i < length(unapprovedVerb); ++i) {
// Add the start condition of the first word so that the end condition is easier
if (!firstWordStarted) {
firstWordStarted = true;
buildFirstWord = true;
} else if (buildFirstWord && (unapprovedVerb.charCodeAt(i) >= 65) && (unapprovedVerb.charCodeAt(i) <= 90)) {
// Stop building the first word when we encounter another capital letter
buildFirstWord = false;
firstWordEnd = i;
}
if (buildFirstWord) {
firstWord += unapprovedVerb.charAt(i);
}
currentVerbCandidate += unapprovedVerb.charAt(i);
if (existsVerb(currentVerbCandidate)) {
// The latest verb match is also the longest verb match
longestVerbMatch = currentVerbCandidate;
verbMatchEnd = i + 1;
}
}
const beginningOfSuffix = longestVerbMatch ? verbMatchEnd : firstWordEnd;
cmdVerb = longestVerbMatch ? longestVerbMatch : firstWord;
if (Object
.keys(cmdVerbMapCustom)
.map(v => v.toLowerCase())
.includes(cmdVerb.toLowerCase())) {
cmdVerbs = mapVerb(cmdVerb);
} else {
cmdVerbs = [cmdVerb];
}
if (-1 !== beginningOfSuffix) {
// This is still empty when a verb match is found that is the entire string, but it might not be worth checking for that case and skipping the below operation
const cmdNounSuffix = unapprovedVerb.substring(beginningOfSuffix);
// Add command noun suffix only when the current noun doesn't contain it or vice-versa.
if (!cmdNoun) {
cmdNoun = pascalCase([cmdNounSuffix]);
} else if (!cmdNounSuffix.toLowerCase().startsWith('by')) {
if (
!cmdNoun.toLowerCase().includes(cmdNounSuffix.toLowerCase()) &&
!cmdNounSuffix.toLowerCase().includes(cmdNoun.toLowerCase())) {
cmdNoun += pascalCase([cmdNounSuffix]);
} else if (cmdNounSuffix.toLowerCase().includes(cmdNoun.toLowerCase())) {
cmdNoun = cmdNounSuffix;
}
}
}
}
}
// Singularize command noun
if (cmdNoun) {
cmdNoun = getSingularizedValue(cmdNoun);
}
return cmdVerbs.map(v => {
let verb = pascalCase([v]);
if (!cmdNoun) { verb = getSingularizedValue(verb); }
onMessage({ Channel: Channel.Debug, Text: `Operation '${operationId}': Using noun '${cmdNoun}' and verb '${verb}'.` });
return { noun: cmdNoun, verb, variant };
});
}