Skip to content

Commit

Permalink
Merge pull request #75 from daniel-sc/73-convert-full-line-comments
Browse files Browse the repository at this point in the history
feat: convert full line comments - fixes #73
  • Loading branch information
daniel-sc authored Jan 20, 2025
2 parents cf4bcc4 + d5db69a commit a82cfae
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 35 deletions.
82 changes: 54 additions & 28 deletions src/convert-bash.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,66 +16,76 @@ describe('convert-bash', () => {
);
});

test('should handle simple full line comment', () => {
expect(convertBashToWin('echo "hi"\n# this is a comment'))
.toEqual('@echo off\necho "hi"\nREM this is a comment');
});

test('should preserve empty lines', () => {
expect(convertBashToWin('echo "hi"\n\n\n# this is a comment'))
.toEqual('@echo off\necho "hi"\n\n\nREM this is a comment');
});

test('should transform single command', () => {
expect(convertBashToWin('rm -rf /c/cygwin/path'))
.toEqual('@echo off\n\nDEL /S "c:\\cygwin\\path"');
.toEqual('@echo off\nDEL /S "c:\\cygwin\\path"');
});

test('should handle "&&"', () => {
expect(convertBashToWin('echo "hi 1" && echo "there 2"'))
.toEqual('@echo off\n\necho "hi 1" && echo "there 2"');
.toEqual('@echo off\necho "hi 1" && echo "there 2"');
});

describe('convertPaths', () => {
test('should convert win path', () => {
expect(convertBashToWin('mycommand /absolutepath'))
.toEqual('@echo off\n\nmycommand "\\absolutepath"');
.toEqual('@echo off\nmycommand "\\absolutepath"');
});
test('should convert cyg win path', () => {
expect(convertBashToWin('mycommand /c/cygwin/path'))
.toEqual('@echo off\n\nmycommand "c:\\cygwin\\path"');
.toEqual('@echo off\nmycommand "c:\\cygwin\\path"');
});
test('should convert relative path', () => {
expect(convertBashToWin('mycommand path/sub'))
.toEqual('@echo off\n\nmycommand "path\\sub"');
.toEqual('@echo off\nmycommand "path\\sub"');
});
test('should convert relative path current dir', () => {
expect(convertBashToWin('mycommand ./path'))
.toEqual('@echo off\n\nmycommand "%CD%\\path"');
.toEqual('@echo off\nmycommand "%CD%\\path"');
});
test('should convert relative path parent dir', () => {
expect(convertBashToWin('mycommand ../path'))
.toEqual('@echo off\n\nmycommand "%CD%\\..\\path"');
.toEqual('@echo off\nmycommand "%CD%\\..\\path"');
});
test('should keep cd to parent dir', () => {
expect(convertBashToWin('cd ..'))
.toEqual('@echo off\n\ncd ".."');
.toEqual('@echo off\ncd ".."');
});
test('should convert file with extension', () => {
expect(convertBashToWin('mycommand path/file.txt'))
.toEqual('@echo off\n\nmycommand "path\\file.txt"');
.toEqual('@echo off\nmycommand "path\\file.txt"');
});
test('should handle simple url', () => {
expect(convertBashToWin('wget https://website.com'))
.toEqual('@echo off\n\nwget "https://website.com"');
.toEqual('@echo off\nwget "https://website.com"');
});
test('should handle url with query', () => {
expect(convertBashToWin('wget https://website.com/path?query=1'))
.toEqual('@echo off\n\nwget "https://website.com/path?query=1"');
.toEqual('@echo off\nwget "https://website.com/path?query=1"');
});
});

test('should handle "||"', () => {
expect(convertBashToWin('echo "hi 1" || echo "there 2"'))
.toEqual('@echo off\n\necho "hi 1" || echo "there 2"');
.toEqual('@echo off\necho "hi 1" || echo "there 2"');
});

test('should handle simple if', () => {
expect(convertBashToWin('if [ "$my_var" == "" ]; then\n' +
' echo "my_var is empty"\n' +
' echo "second line"\n' +
'fi'))
.toEqual('@echo off\n\nIF "%my_var%" == "" (\n echo "my_var is empty"\n echo "second line"\n)');
.toEqual('@echo off\nIF "%my_var%" == "" (\n echo "my_var is empty"\n echo "second line"\n)');
});

test('should handle if-else', () => {
Expand All @@ -84,77 +94,77 @@ describe('convert-bash', () => {
'else\n' +
' echo "my_var is not empty"\n' +
'fi'))
.toEqual('@echo off\n\nIF "%my_var%" == "" (\n echo "my_var is empty"\n) ELSE (\n echo "my_var is not empty"\n)');
.toEqual('@echo off\nIF "%my_var%" == "" (\n echo "my_var is empty"\n) ELSE (\n echo "my_var is not empty"\n)');
});

test('should handle simple if -eq', () => {
expect(convertBashToWin('if [ "$my_var" -eq "" ]; then\n' +
' echo "my_var is empty"\n' +
' echo "second line"\n' +
'fi'))
.toEqual('@echo off\n\nIF "%my_var%" EQU "" (\n echo "my_var is empty"\n echo "second line"\n)');
.toEqual('@echo off\nIF "%my_var%" EQU "" (\n echo "my_var is empty"\n echo "second line"\n)');
});

test('should handle simple if -ne', () => {
expect(convertBashToWin('if [ "$my_var" -ne "" ]; then\n' +
' echo "my_var is empty"\n' +
' echo "second line"\n' +
'fi'))
.toEqual('@echo off\n\nIF "%my_var%" NEQ "" (\n echo "my_var is empty"\n echo "second line"\n)');
.toEqual('@echo off\nIF "%my_var%" NEQ "" (\n echo "my_var is empty"\n echo "second line"\n)');
});

test('should handle simple if -lt', () => {
expect(convertBashToWin('if [ "$my_var" -lt "" ]; then\n' +
' echo "my_var is empty"\n' +
' echo "second line"\n' +
'fi'))
.toEqual('@echo off\n\nIF "%my_var%" LSS "" (\n echo "my_var is empty"\n echo "second line"\n)');
.toEqual('@echo off\nIF "%my_var%" LSS "" (\n echo "my_var is empty"\n echo "second line"\n)');
});

test('should handle simple if -le', () => {
expect(convertBashToWin('if [ "$my_var" -le "" ]; then\n' +
' echo "my_var is empty"\n' +
' echo "second line"\n' +
'fi'))
.toEqual('@echo off\n\nIF "%my_var%" LEQ "" (\n echo "my_var is empty"\n echo "second line"\n)');
.toEqual('@echo off\nIF "%my_var%" LEQ "" (\n echo "my_var is empty"\n echo "second line"\n)');
});

test('should handle simple if -gt', () => {
expect(convertBashToWin('if [ "$my_var" -gt "" ]; then\n' +
' echo "my_var is empty"\n' +
' echo "second line"\n' +
'fi'))
.toEqual('@echo off\n\nIF "%my_var%" GTR "" (\n echo "my_var is empty"\n echo "second line"\n)');
.toEqual('@echo off\nIF "%my_var%" GTR "" (\n echo "my_var is empty"\n echo "second line"\n)');
});

test('should handle simple if -ge', () => {
expect(convertBashToWin('if [ "$my_var" -ge "" ]; then\n' +
' echo "my_var is empty"\n' +
' echo "second line"\n' +
'fi'))
.toEqual('@echo off\n\nIF "%my_var%" GEQ "" (\n echo "my_var is empty"\n echo "second line"\n)');
.toEqual('@echo off\nIF "%my_var%" GEQ "" (\n echo "my_var is empty"\n echo "second line"\n)');
});

test('should handle simple if not equal', () => {
expect(convertBashToWin('if [ ! "$my_var" == "" ]; then\n' +
' echo "my_var is empty"\n' +
' echo "second line"\n' +
'fi'))
.toEqual('@echo off\n\nIF NOT "%my_var%" == "" (\n echo "my_var is empty"\n echo "second line"\n)');
.toEqual('@echo off\nIF NOT "%my_var%" == "" (\n echo "my_var is empty"\n echo "second line"\n)');
});

test('should handle string interpolation with backticks', () => {
expect(convertBashToWin('my_var="test-`git log`"'))
.toEqual('@echo off\n' +
'setlocal EnableDelayedExpansion\n\n' +
'setlocal EnableDelayedExpansion\n' +
'SET _INTERPOLATION_0=\n' +
'FOR /f "delims=" %%a in (\'git log\') DO (SET "_INTERPOLATION_0=!_INTERPOLATION_0! %%a")\n' +
'SET "my_var=test-!_INTERPOLATION_0:~1!"');
});
test('should echo variable correctly with delayed expansion', () => {
expect(convertBashToWin('my_var="test-`git log`"\necho $my_var'))
.toEqual('@echo off\n' +
'setlocal EnableDelayedExpansion\n\n' +
'setlocal EnableDelayedExpansion\n' +
'SET _INTERPOLATION_0=\n' +
'FOR /f "delims=" %%a in (\'git log\') DO (SET "_INTERPOLATION_0=!_INTERPOLATION_0! %%a")\n' +
'SET "my_var=test-!_INTERPOLATION_0:~1!"\n' +
Expand All @@ -169,7 +179,6 @@ describe('convert-bash', () => {
setlocal EnableDelayedExpansion
EXIT /B %ERRORLEVEL%
:my_function
Expand All @@ -184,7 +193,7 @@ EXIT /B 0
test('should handle string interpolation with dollar brackets', () => {
expect(convertBashToWin('my_var="test-$(git log)"'))
.toEqual('@echo off\n' +
'setlocal EnableDelayedExpansion\n\n' +
'setlocal EnableDelayedExpansion\n' +
'SET _INTERPOLATION_0=\n' +
'FOR /f "delims=" %%a in (\'git log\') DO (SET "_INTERPOLATION_0=!_INTERPOLATION_0! %%a")\n' +
'SET "my_var=test-!_INTERPOLATION_0:~1!"');
Expand All @@ -203,7 +212,6 @@ EXIT /B 0
' ;;\n' +
'esac'))
.toEqual('@echo off\n' +
'\n' +
'IF "%~1"=="Darwin" (\n' +
' echo "found darwin"\n' +
') ELSE IF "%~1"=="Jesus" (\n' +
Expand All @@ -213,13 +221,29 @@ EXIT /B 0
')');
});

test('should handle function declaration with keyword at start', () => {
test('should handle function declaration with comment', () => {
expect(convertBashToWin(`function my_function () {
# this is a comment
echo "hello from my_function: $1"
}`))
.toEqual(`@echo off
EXIT /B %ERRORLEVEL%
:my_function
REM this is a comment
echo "hello from my_function: %~1"
EXIT /B 0
`);
});

test('should handle function declaration with keyword at start', () => {
expect(convertBashToWin(`function my_function () {
echo "hello from my_function: $1"
}`))
.toEqual(`@echo off
EXIT /B %ERRORLEVEL%
Expand All @@ -235,7 +259,6 @@ EXIT /B 0
echo "hello from my_function: $1"
}`))
.toEqual(`@echo off
echo "test"
EXIT /B %ERRORLEVEL%
Expand Down Expand Up @@ -266,7 +289,10 @@ my_function "some param"
SET "SOME_VAR=c:\\cygwin\\path"
DEL /S "%SOME_VAR%"
COPY "c:\\some\\file" "\\to\\another\\file"
REM call the function:
CALL :my_function
REM call the function with param:
CALL :my_function "some param"
EXIT /B %ERRORLEVEL%
Expand Down
23 changes: 16 additions & 7 deletions src/convert-bash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,11 @@ class ConvertBash {
result = `${result.substring(0, expansion.loc.start)}!${interpolationVar}:~1!${result.substring(expansion.loc.end + 1)}`;
break;
case 'ParameterExpansion':
// expand function parameters such as `$1` (-> `%~1`) different to regular variables `$MY`(-> `%MY%` or `!MY!` if delayed expansion is active):
const expandedValue = /^\d+$/.test(`${expansion.parameter}`) ? `%~${expansion.parameter}` : (this.delayedExpansionActive ? `!${expansion.parameter}!` : `%${expansion.parameter}%`);
result = `${result.substring(0, expansion.loc.start)}${expandedValue}${result.substring(expansion.loc.end + 1)}`;
if (expansion.loc.start >= 0 && expansion.loc.end >= 0) { // in test 'should transform complete example' the loc is negative (and there is no meaningful expansion at that point)
// expand function parameters such as `$1` (-> `%~1`) different to regular variables `$MY`(-> `%MY%` or `!MY!` if delayed expansion is active):
const expandedValue = /^\d+$/.test(`${expansion.parameter}`) ? `%~${expansion.parameter}` : (this.delayedExpansionActive ? `!${expansion.parameter}!` : `%${expansion.parameter}%`);
result = `${result.substring(0, expansion.loc.start)}${expandedValue}${result.substring(expansion.loc.end + 1)}`;
}
break;
}
}
Expand All @@ -184,7 +186,14 @@ class ConvertBash {


function preprocess(script: string): string {
return script.replace(/(^|\n)\s*function /g, '$1');
return script.replace(/(^|\n)\s*function /g, '$1')
.replace(/\n(?=\n)/g, '\n_BLANK_LINE_="x"')
.replace(/(^|\n)(?!#!)\s*#(.*)/g, '$1_COMMENT_="$2"');
}

function postprocess(script: string): string {
return script.replace(/SET "_COMMENT_= ?(.*)"/g, 'REM $1')
.replace(/SET "_BLANK_LINE_=x"/g, '');
}

export function convertBashToWin(script: string) {
Expand All @@ -198,7 +207,7 @@ export function convertBashToWin(script: string) {
const functionDefinitions = converter.getFunctionDefinitions();
return '@echo off' +
(converter.delayedExpansion() ? '\nsetlocal EnableDelayedExpansion' : '') +
'\n\n' +
convertedCommands +
functionDefinitions;
'\n' +
postprocess(convertedCommands +
functionDefinitions);
}

0 comments on commit a82cfae

Please sign in to comment.