diff --git a/public/blocks/argumentBlock.js b/public/blocks/argumentBlock.js index 2e926e1..4eb96e3 100644 --- a/public/blocks/argumentBlock.js +++ b/public/blocks/argumentBlock.js @@ -5,15 +5,14 @@ var argumentBlock = { args0: [ { type: 'field_input', - name: 'argument', + name: 'ARGUMENT', text: 'argument' // default text for the input } ], - + extensions: ['restrict_argumentsCreate_to_argument'], output: 'String', style: 'Function inputs', tooltip: '%{BKY_ARGUMENT_TOOLTIP}', helpUrl: '%{BKY_ARGUMENT_HELPURL}' // URL για περαιτέρω πληροφορίες ή τεκμηρίωση. }; - Blockly.defineBlocksWithJsonArray([argumentBlock]); diff --git a/public/blocks/fileEndStartBlock.js b/public/blocks/fileEndStartBlock.js index 8af4d3f..ead3405 100644 --- a/public/blocks/fileEndStartBlock.js +++ b/public/blocks/fileEndStartBlock.js @@ -24,11 +24,10 @@ var fileEndStartBlock = { text: '....' // empty text for user to define filename } ], - + extensions: ['restrict_fileEndStart_to_filenamesCreate'], output: 'fileWildcard', style: 'Function inputs', tooltip: '%{BKY_FILE_END_START_WILDCHARS_TOOLTIP}', helpUrl: '%{BKY_FILE_END_START_WILDCHARS_HELPURL} ' // URL to further information or documentation. }; - Blockly.defineBlocksWithJsonArray([fileEndStartBlock]); diff --git a/public/blocks/filenameBlock.js b/public/blocks/filenameBlock.js index 4e0ff64..5ccf6f7 100644 --- a/public/blocks/filenameBlock.js +++ b/public/blocks/filenameBlock.js @@ -9,10 +9,10 @@ var filenameBlock = { text: 'default.txt' // default text for the input } ], + extensions: ['restrict_filename_to_filenamesCreate'], output: 'filename', style: 'Function inputs', tooltip: '%{BKY_FILENAME_TOOLTIP}', helpUrl: '%{BKY_FILENAME_HELPURL}' // URL to further information or documentation. }; - Blockly.defineBlocksWithJsonArray([filenameBlock]); diff --git a/public/blocks/mvBlock.js b/public/blocks/mvBlock.js index ae496e8..49026d5 100644 --- a/public/blocks/mvBlock.js +++ b/public/blocks/mvBlock.js @@ -36,12 +36,12 @@ var mvBlock = { message4: '%{BKY_MV_SOURCE}: %1 %{BKY_MV_DEST}: %2', args4: [ { - type: 'field_input', + type: 'input_value', name: 'SOURCE', text: 'source' }, { - type: 'field_input', + type: 'input_value', name: 'DEST', text: 'destination' } diff --git a/public/blocks/rmBlock.js b/public/blocks/rmBlock.js index 98ed0b4..b5192b4 100644 --- a/public/blocks/rmBlock.js +++ b/public/blocks/rmBlock.js @@ -1,6 +1,5 @@ var rmBlock = { type: 'rm', - message0: '%{BKY_RM}', category: 'Filesystem Operations', unix_description: [ { @@ -11,6 +10,14 @@ var rmBlock = { undelete: '-W' } ], + message0: '%{BKY_RM} %1', + args0: [ + { + type: 'input_value', + name: 'ARGUMENT', + check: 'String' + } + ], message1: '%{BKY_RM_FORCE}', args1: [ { diff --git a/public/blocks/sedBlock.js b/public/blocks/sedBlock.js index f5ec16f..2ad65b0 100644 --- a/public/blocks/sedBlock.js +++ b/public/blocks/sedBlock.js @@ -21,7 +21,7 @@ var sedBlock = { message2: '%{BKY_SED_PATTERN}', args2: [ { - type: 'input_value', + type: 'input_statement', name: 'regPattern', check: 'String' } diff --git a/public/blocks/touchBlock.js b/public/blocks/touchBlock.js index bd77754..d09233f 100644 --- a/public/blocks/touchBlock.js +++ b/public/blocks/touchBlock.js @@ -55,10 +55,78 @@ var touchBlock = { } ], - extensions: ['validate_touch_time_d'], + extensions: ['validate_touch_time_d', 'restrict_touch_to_argumentsCreate'], style: 'Filesystem Operations', tooltip: '%{BKY_TOUCH_TOOLTIP}', helpUrl: '%{BKY_TOUCH_HELPURL}' // URL to further information or documentation. }; Blockly.defineBlocksWithJsonArray([touchBlock]); + +Blockly.Extensions.register('validate_touch_time_d', function () { + this.getField('change_time_d').setValidator(function (input) { + // Define regex patterns to match different valid formats + var patterns = [ + /^$/, // Empty string pattern + /^(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])\s(0[0-9]|1[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])(Z)?$/, // YYYY-MM-DD hh:mm:SS[tz] + /^(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T(0[0-9]|1[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])(Z)?$/ // YYYY-MM-DDThh:mm:SS[tz] + ]; + + // Check if input matches one of the valid patterns + var isValid = patterns.some(function (pattern) { + return pattern.test(input); + }); + + if (!isValid) { + return null; // Invalid input, reject + } + + // Extract components from input (no replacement here) + var parts = input.split('T'); + var datePart = parts[0]; + var timePart = (parts[1] || '').split('.')[0]; // Remove fractional seconds part + var tzPart = (parts[1] || '').includes('Z') ? 'Z' : ''; + + // Handle date part + var dateParts = datePart.split('-'); + var year = dateParts[0]; + var month = dateParts[1]; + var day = dateParts[2]; + + // Handle time part + var timeParts = timePart.split(':'); + var hour = timeParts[0] || '00'; + var minute = timeParts[1] || '00'; + var second = timeParts[2] || '00'; + + // Validate ranges for year, month, day, hour, minute, and second + if ( + month < '01' || + month > '12' || + day < '01' || + day > '31' || + hour < '00' || + hour > '23' || + minute < '00' || + minute > '59' || + second < '00' || + second > '59' + ) { + return null; // Invalid input: reject + } + + // Check day validity for the given month and year (leap year handling) + var daysInMonth = new Date(year, parseInt(month, 10), 0).getDate(); + if (parseInt(day, 10) > daysInMonth) { + return null; // Invalid day for the given month + } + + // Optional time zone part validation + if (tzPart && tzPart !== 'Z') { + return null; // Invalid time zone designator, should be 'Z' if present + } + + // Replace space with 'T' only when returning the valid input + return input; + }); +}); diff --git a/public/js/block.js b/public/js/block.js index d86144a..fbf0074 100644 --- a/public/js/block.js +++ b/public/js/block.js @@ -89,9 +89,7 @@ document const changeModificationTime = currentBlock.getFieldValue('modification_time') === 'TRUE'; const rawTimestamp = currentBlock.getFieldValue('change_time_d'); - - // Check if the previous block passed a filename - let previousBlock = currentBlock.getPreviousBlock(); + const argumentBlock = currentBlock.getInputTargetBlock('argument'); if (notCreateFile) { touchCommand += ' -c'; @@ -113,7 +111,7 @@ document // Add the constructed touch command to the generatedCommand generatedCommand += (generatedCommand ? ' | ' : '') + touchCommand; - generatedCommand += ' ' + handleFilenamesBlocks(previousBlock); + generatedCommand += ' ' + handleArgumentsBlocks(argumentBlock); } else if (currentBlock.type === 'sed') { let sedCommand = handleBlock(currentBlock); // Generates the basic sed command // Get the pattern input and replacement field input @@ -167,6 +165,83 @@ document } // Add the constructed sed command to the generatedCommand generatedCommand += (generatedCommand ? ' | ' : '') + sedCommand; + } else if (currentBlock.type === 'ln') { + let lnCommand = 'ln'; + const symbolic = currentBlock.getFieldValue('symbolic') === 'TRUE'; + const force = currentBlock.getFieldValue('force') === 'TRUE'; + const interactive = + currentBlock.getFieldValue('interactive') === 'TRUE'; + + if (symbolic) { + lnCommand += ' -s '; + } + + if (force) { + lnCommand += ' -f '; + } + + if (interactive) { + lnCommand += ' -i '; + } + const sourceArgsBlock = currentBlock.getInputTargetBlock('SOURCE'); + const targetArgsBlock = currentBlock.getInputTargetBlock('TARGET'); + lnCommand += handleArgumentsBlocks(sourceArgsBlock); + lnCommand += ' ' + handleArgumentsBlocks(targetArgsBlock); + generatedCommand += (generatedCommand ? ' | ' : '') + lnCommand; + } else if (currentBlock.type === 'mv') { + let mvCommand = 'mv'; // Start with the basic mv command + const notPromptConfirmation = + currentBlock.getFieldValue('not_prompt_confirmation') === 'TRUE'; + const promptConfirmation = + currentBlock.getFieldValue('prompt_confirmation') === 'TRUE'; + const notOverwrite = + currentBlock.getFieldValue('not_overwrite') === 'TRUE'; + const sourceArgsBlock = currentBlock.getInputTargetBlock('SOURCE'); + const targetArgsBlock = currentBlock.getInputTargetBlock('DEST'); + + if (notPromptConfirmation) { + mvCommand += ' -f '; + } + + if (promptConfirmation) { + mvCommand += ' -i '; + } + + if (notOverwrite) { + mvCommand += ' -n '; + } + + mvCommand += handleArgumentsBlocks(sourceArgsBlock); + mvCommand += ' ' + handleArgumentsBlocks(targetArgsBlock); + generatedCommand += (generatedCommand ? ' | ' : '') + mvCommand; + } else if (currentBlock.type === 'rm') { + let rmCommand = 'rm'; // Start with the basic rm command + const force = currentBlock.getFieldValue('force') === 'TRUE'; + const requestConfirmation = + currentBlock.getFieldValue('request_confirmation') === 'TRUE'; + const removeDirectory = + currentBlock.getFieldValue('remove_directory') === 'TRUE'; + const recursive = currentBlock.getFieldValue('recursive') === 'TRUE'; + + if (force) { + rmCommand += ' -f '; + } + + if (requestConfirmation) { + rmCommand += ' -i '; + } + + if (removeDirectory) { + rmCommand += ' -d '; + } + + if (recursive) { + rmCommand += ' -R '; + } + + const argumentBlock = currentBlock.getInputTargetBlock('ARGUMENT'); + rmCommand += ' ' + handleArgumentsBlocks(argumentBlock); + generatedCommand += (generatedCommand ? ' | ' : '') + rmCommand; } else { generatedCommand += (generatedCommand ? ' | ' : '') + handleBlock(currentBlock); @@ -509,6 +584,27 @@ function handleBlock(block) { return commandString; } +function handleArgumentsBlocks(block) { + console.log('handleArgumentsBlocks - init'); + + var arguments = ''; + + if (block && block.type === 'argumentsCreate') { + console.log('handleArgumentsBlocks - block:', block.type); + let args = []; + for (let i = 0; i < block.itemCount_; i++) { + let inputBlock = block.getInputTargetBlock('ADD' + i); + if (inputBlock) { + let arg = inputBlock.getFieldValue('ARGUMENT'); + if (arg) { + args.push(arg); + } + } + } + return args.join(' '); + } +} + function handleMainBlocks( block, blockDefinition, @@ -1113,6 +1209,79 @@ Blockly.Extensions.register('integer_validation', function () { }); }); +function registerConnectionRestrictionExtension( + extensionName, + parentBlockType, + inputFieldName, + allowedBlockType +) { + Blockly.Extensions.register(extensionName, function () { + this.setOnChange(function (changeEvent) { + if ( + changeEvent.type === Blockly.Events.BLOCK_MOVE && + this.id === changeEvent.blockId + ) { + var parentBlock = this.getParent(); + if (parentBlock && parentBlock.type === parentBlockType) { + var inputConnection = parentBlock + .getInput(inputFieldName) + .connection.targetBlock(); + + // If the connected block is not this block, or if it's not the allowed block type, disconnect + if ( + (inputConnection && inputConnection !== this) || + (allowedBlockType && this.type !== allowedBlockType) + ) { + if (this.outputConnection.targetConnection) { + this.outputConnection.disconnect(); + console.warn( + `${this.type} block can only connect to the ${inputFieldName} input of ${parentBlockType} block.` + ); + } + } + } else { + // If the parent block is not of the expected type, disconnect + if (this.outputConnection.targetConnection) { + this.outputConnection.disconnect(); + console.warn( + `${this.type} block can only connect to ${parentBlockType} block.` + ); + } + } + } + }); + }); +} + +// Register the connection restriction for the 'argumentsCreate' block +registerConnectionRestrictionExtension( + 'restrict_argumentsCreate_to_argument', + 'argumentsCreate', + 'EMPTY', + 'argument' +); + +registerConnectionRestrictionExtension( + 'restrict_filename_to_filenamesCreate', + 'filenamesCreate', + 'EMPTY', + 'filename' +); + +registerConnectionRestrictionExtension( + 'restrict_fileEndStart_to_filenamesCreate', + 'filenamesCreate', + 'EMPTY', + 'fileEndStart' +); + +registerConnectionRestrictionExtension( + 'restrict_touch_to_argumentsCreate', + 'argumentsCreate', + 'EMPTY', + 'touch' +); + Blockly.Extensions.register('disallow_multiple_filenames', function () { this.setOnChange(function (changeEvent) { if ( @@ -1168,74 +1337,6 @@ Blockly.Extensions.register('cut_validation', function () { }); }); -Blockly.Extensions.register('validate_touch_time_d', function () { - this.getField('change_time_d').setValidator(function (input) { - // Define regex patterns to match different valid formats - var patterns = [ - /^$/, // Empty string pattern - /^(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])\s(0[0-9]|1[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])(Z)?$/, // YYYY-MM-DD hh:mm:SS[tz] - /^(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T(0[0-9]|1[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])(Z)?$/ // YYYY-MM-DDThh:mm:SS[tz] - ]; - - // Check if input matches one of the valid patterns - var isValid = patterns.some(function (pattern) { - return pattern.test(input); - }); - - if (!isValid) { - return null; // Invalid input, reject - } - - // Extract components from input (no replacement here) - var parts = input.split('T'); - var datePart = parts[0]; - var timePart = (parts[1] || '').split('.')[0]; // Remove fractional seconds part - var tzPart = (parts[1] || '').includes('Z') ? 'Z' : ''; - - // Handle date part - var dateParts = datePart.split('-'); - var year = dateParts[0]; - var month = dateParts[1]; - var day = dateParts[2]; - - // Handle time part - var timeParts = timePart.split(':'); - var hour = timeParts[0] || '00'; - var minute = timeParts[1] || '00'; - var second = timeParts[2] || '00'; - - // Validate ranges for year, month, day, hour, minute, and second - if ( - month < '01' || - month > '12' || - day < '01' || - day > '31' || - hour < '00' || - hour > '23' || - minute < '00' || - minute > '59' || - second < '00' || - second > '59' - ) { - return null; // Invalid input: reject - } - - // Check day validity for the given month and year (leap year handling) - var daysInMonth = new Date(year, parseInt(month, 10), 0).getDate(); - if (parseInt(day, 10) > daysInMonth) { - return null; // Invalid day for the given month - } - - // Optional time zone part validation - if (tzPart && tzPart !== 'Z') { - return null; // Invalid time zone designator, should be 'Z' if present - } - - // Replace space with 'T' only when returning the valid input - return input; - }); -}); - //*********************************** //EXTENSIONS FOR VALIDATIONS - END //***********************************