Action
RegEx Factory v.1.4
Multiple RegEx with preview and replace (or cancel).
- New: Run on selected text or whole draft if no selection.
- Save RegEx group expressions for future use.
- Each RegEx group can have one or more pairs of find and replace patterns.
- Generate RegEx JS script action as new draft in Inbox :)
- New: Easy ‘test and add’ new patterns to groups.
- New: Output to new draft or clipboard.
- New: Edit all, to delete or editing groups.
Steps
-
script
// RegEx Factory v.1.4.4 // RV 2020-09-12 at 19:56 EST // Multiple RegEx with preview/cancel or replace. // RegEx group expressions are automatically saved // to a text file in iCloud Drive on 'Preview/Run...' // Each RegEx group can have one or more replace patterns. // Each pattern is one line in the format: /regex find/, "replace" // RegEx swithes 'gm' are on by default (but can be changed) // Output to current draft, new draft or clipboard. // Run on selected text or whole draft if no selection. // Generate RegEx JS script action as new draft in Inbox :) 'use strict'; // Global variables: var path = '/RegExGroups.txt'; var groupTitle = ''; var regExGroup = ''; var regExGroupList = []; var regExGroupIndex = -1; var startLastUsed = false; var find = ''; var repl = ''; var swi = false; var selRange = []; var draftText = ''; function main() { // Run on selected text or full draft if no selection: selRange = editor.getSelectedRange(); if (selRange[1] == 0) { draftText = editor.getText(); } else { draftText = editor.getSelectedText(); } let isRead = readGroupFile(); if (selectGroup(isRead)) { while(regExFactory()); // Loops until false (done or canceled) } else { context.cancel('Canceled by user'); } } function readGroupFile() { let fmCloud = FileManager.createCloud(); let groupsText = fmCloud.readString(path); if (!groupsText) { // No file found, use sample text instead: // see 'groupSample' at end of script. loadGroups(groupSample); } else { loadGroups(groupsText); } } function loadGroups(groupsText) { regExGroupList = []; let groups = groupsText.split('\n# '); let index = parseInt(groups[0].slice(12)); if (index > -1) startLastUsed = true; for (let group of groups.slice(1)) { group = group.replace('\n', '\r'); let item = group.split('\r'); let title = item[0].replace(/^# /, ''); let gRegEx = item[1].trim(); regExGroupList.push([title, gRegEx]); } if (index == -1 || index >= regExGroupList.length) { regExGroupIndex = 0; getGroup(regExGroupIndex) return true; } else { getGroup(index); return false; } } function selectGroup(load) { if (!load) return true; let index = regExGroupIndex; if (regExGroupIndex == -1) { index = 0; } let p = Prompt.create(); p.title = "Load RegEx Factory"; p.addPicker('group', 'Group:', [listGroupTitles()], [index]); p.addSwitch('start', 'Start With Last Used', startLastUsed); p.addButton('Load Group'); p.addButton('New Empty Group'); p.addButton('Edit All Groups...'); if (p.show()) { startLastUsed = p.fieldValues['start']; switch (p.buttonPressed) { case 'Load Group': getGroup(parseInt(p.fieldValues['group'])); return false; case 'New Empty Group': groupTitle = ''; regExGroup = ''; regExGroupIndex = -1; return false; case 'Edit All Groups...': editAllGroups(); return true; default: app.displayErrorMessage("switch/case not implemented!"); return true; } return true; } return false; } function editAllGroups() { // regExGroupList.push([title, gRegEx]) let groups = []; for (const group of regExGroupList) { groups.push('# ' + group[0]); // Group title groups.push(group[1] + '\n'); // Group body } let groupsText = groups.join('\n'); let p = Prompt.create(); p.title = 'Edit or Delete Groups:'; p.message = 'Format: \n# Group title\n/find pattern/gm, "replace"'; p.addTextView('text', 'RegEx Groups:', groupsText, {'height': 300}) p.addButton('Save changes'); if (p.show()) { let groupsText2 = p.fieldValues['text']; if (groupsText2.split('\n').length < 2) { app.displayErrorMessage("Wrong format!"); return false; } if (p.buttonPressed == 'Save changes' && groupsText2 != groupsText) { let index = regExGroupIndex; if (startLastUsed) { groupsText2 = 'StartIndex = ' + regExGroupIndex + '\n' + groupsText2; } else { groupsText2 = 'StartIndex = -1\n' + groupsText2; } try { loadGroups(groupsText2); // saveGroupFile(regExGroupIndex); } catch(err) { alert(err.message); return false; } return true; } } else { return false; } } function getGroup(index) { if (index == -1) return; groupTitle = regExGroupList[index][0]; regExGroup = regExGroupList[index][1]; regExGroupIndex = index; } function listGroupTitles() { let titleList = []; for (const group of regExGroupList) { titleList.push(group[0]); } return titleList; } function regExFactory() { let p = Prompt.create(); p.title = "RegEx Factory"; p.addTextField("groupTitle", "Title:", groupTitle, {"wantsFocus": true }); p.addTextView('text', 'RegExGroup:', regExGroup) p.addButton('Test & Run on Current Draft...'); p.addButton('Run to New Draft'); p.addButton('Run to Clipboard'); p.addButton('Add New Pattern...'); p.addButton('Load Other Groups...'); p.addButton('Save as New...'); p.addButton('JS Code to Inbox'); p.addButton('RegEx Help...'); if (p.show()) { groupTitle = p.fieldValues['groupTitle']; regExGroup = p.fieldValues['text']; switch (p.buttonPressed) { case 'Add New Pattern...': while (addPattern(regExGroup)); // Loops until false = done or canceled break; case 'Load Other Groups...': while(selectGroup(true)); break; case 'Save as New...': saveAsNew(); break; case 'Test & Run on Current Draft...': return (!runRegEx('current')); case 'Run to New Draft': return (!runRegEx('new')); case 'Run to Clipboard': return (!runRegEx('clipboard')); case 'JS Code to Inbox': generateJsCode(regExGroup); break; case 'RegEx Help...': app.openURL('https://regex101.com') break; default: app.displayErrorMessage("switch/case not implemented!") ; break; } return true; // continue while loop } else { context.cancel('Canceled by user!'); return false; // end while loop } } function saveAsNew() { if (regExGroup.trim() == '') { app.displayErrorMessage('RegEx arguments missing!'); return false; } let p = Prompt.create(); p.title = 'Save as New Group'; p.message = regExGroup; p.addTextField('name', 'New Group Name:', groupTitle); p.addButton('Save'); if (p.show()) { if (p.buttonPressed == 'Save') { groupTitle = p.fieldValues['name']; saveGroupFile(-1); return true; } } return false; } function runRegEx(type) { if (regExGroup.trim() == '') { app.displayErrorMessage('RegEx arguments missing!'); return false; } // Replace text with regExGroup: const [text, group] = replaceByRegexGroup(draftText, regExGroup); if (text == '') { app.displayErrorMessage('Empty result!'); return false; } saveGroupFile(regExGroupIndex); switch (type) { case 'current': if (preview(text, group)) { if (selRange[1]== 0) { editor.setText(text); } else { editor.setSelectedText(text); let diff = text.length - draftText.length; editor.setSelectedRange(selRange[0], selRange[1] + diff); } draft.addTag('regexed'); app.displayInfoMessage('Draft is "RegExed"!'); return true; } return false; case 'new': let d = Draft.create(); d.content = text; d.languageGrammar = draft.languageGrammar; d.update(); for (const tag of draft.tags) { d.addTag(tag); } // d.isFlagged = draft.isFlagged; d.addTag('regexed'); d.update(); editor.load(d); app.displayInfoMessage('"RegExed" as new draft!'); return true; case 'clipboard': app.setClipboard(text); app.displayInfoMessage('"RegExed" to clipboard!'); return true; default: app.displayErrorMessage('switch/case not implemented!'); return false; } } function addPattern() { let p = Prompt.create(); p.title = 'New Pattern'; p.message = 'Add to: "' + groupTitle + '"'; p.addTextField('find', 'Find regx:', find); p.addTextField('repl', 'Replace: ', repl); p.addSwitch('swi', 'Case Insensitive', swi); p.addButton('Test and Add Pattern...'); p.addButton('Test with Current Group...'); p.addButton('Add without Testing...'); if (p.show()) { find = p.fieldValues['find']; repl = p.fieldValues['repl']; swi = p.fieldValues['swi']; let i = '' if (swi) { i = 'gmi'; // add 'i' with default 'gm' } else { i = 'gm'; // add as default } let pattern = `/${find}/${i}, "${repl}"`; if (find == '') { app.displayInfoMessage('Find field: is empty!'); return true; } let text = ''; let group = ''; let ln = ''; if (regExGroup.trim() != '') ln = '\n'; switch (p.buttonPressed) { case 'Test and Add Pattern...': [text, group] = replaceByRegexGroup(draftText, pattern); if (preview(text, group, true)) { regExGroup = regExGroup.trim() + ln + pattern; if (text) saveGroupFile(regExGroupIndex); return false; } break; case 'Test with Current Group...': let reGroup = regExGroup.trim() + ln + pattern; [text, group] = replaceByRegexGroup(draftText, reGroup); if (preview(text, group, true)) { regExGroup = reGroup; if (text) saveGroupFile(regExGroupIndex); return false; } break; case 'Add without Testing...': regExGroup = regExGroup.trim() + ln + pattern; saveGroupFile(regExGroupIndex); return false; default: app.displayErrorMessage('switch/case not implemented!'); break; } return true; // Continue loop } return false; } function saveGroupFile(ind) { let title = groupTitle; if (groupTitle == '') { title = 'Untitled'; } if (ind == -1) { regExGroupList.push([title, regExGroup]); regExGroupIndex = regExGroupList.length - 1 } else { regExGroupList[regExGroupIndex] = [title, regExGroup]; } let gList = []; if (startLastUsed) { gList = ['StartIndex = ' + regExGroupIndex] } else { gList = ['StartIndex = -1'] } for (const group of regExGroupList) { gList.push('# ' + group[0]); // groupTitle gList.push(group[1]); // regExGroup } let fmCloud = FileManager.createCloud(); let success = fmCloud.write(path, gList.join('\n')); } function replaceByRegexGroup(text, reGroup) { for (const row of reGroup.split('\n')) { // Skip commented and blank lines: if (row.startsWith('// ') || row.trim() == '') continue; let m = row.match(/^\/(.+?)\/([gmiyus]{0,6}), ?\"(.*)\"/) if (!m) { app.displayErrorMessage("Error in line syntax: " + row) ; return ['', reGroup]; } let fnd = m[1]; let sw = m[2]; let rep = m[3]; if (sw == '') { // If adding any switches, please include 'gm' as well! sw = 'gm'; // Default RegEx switch } let reFind = new RegExp(fnd, sw); rep = rep.replace(/\\n/g, '\n'); rep = rep.replace(/\\t/g, '\t'); rep = rep.replace(/\\r/g, '\r'); text = text.replace(reFind, rep); } if (text.trim() == '') { app.displayErrorMessage("Empty text result!") ; return ['', reGroup]; } return [text, reGroup]; } function preview(text, group, testPattern=false) { let message = ''; if (testPattern) { message = 'Press "Continue" to add pattern to current group!'; } else { message = 'Press "Continue" to replace current draft!' } // Test: < > "' & text = text.replace(/</g, '<'); // text = text.replace(/>/g, '>'); // text = text.replace(/&/g, '&'); // text = text.replace(/'/g, "'"); // text = text.replace(/"/g, '"'); if (app.currentThemeMode == 'dark') { var css = "body { background: #222; color: #ddd; }"; } else { var css = "body { background: #fff; color: #444; }"; } // add and show default switches 'gm' (if none): let groupWithSwitches = group.replace(/^(\/.+?\/)(?!\w+)(, ?".*")/gm, '$1gm$2'); let html = `<html><style>${css}</style><body> <h3>Result-text preview below line<h3> <h4>${message}<br> or "Cancel\" to exit with no changes.</h4> <h4>RegEx group used: "${groupTitle}\"</h4> <pre>${groupWithSwitches}</pre> <p>(If no switches specified, 'gm\' = "Global, Multiline\" are added by default)</p> <hr> <pre>${text}</pre> </body></html>` let preview = HTMLPreview.create(); return preview.show(html); } function generateJsCode(reGroup) { // Generates JS replace function snippet as draft in Inbox const now = new Date() const date = now.toString("yyyy-MM-dd") const time = now.toString("HH:mm") let codeLines = []; codeLines.push(`// RegEx Replace: ${groupTitle}`) codeLines.push(`// Code generated by "RegEx Factory": ${date} at ${time}`); codeLines.push(''); makeDraftActionCode(codeLines); // Make Drafts Action Script (add comment to skip) codeLines.push('function regExReplace(text) {'); codeLines.push(` // Group Title: ${groupTitle}`) for (const row of reGroup.split('\n')) { if (row.startsWith('// ') || row.trim() == '') { // Blank and commented lines and set indent: codeLines.push(row.replace(/^\/\/ /, ' \/\/ ')); continue; } let m = row.match(/^\/(.+?)\/([gmi]{0,3}), ?\"(.*)\"/) if (!m) { app.displayErrorMessage("Error in line syntax: " + row) ; return ['', reGroup]; } let fnd = m[1]; let rep = m[3]; let sw = m[2]; if (sw == '') { sw = 'gm'; // Default RegEx switch } let line = ` text = text.replace(/${fnd}/${sw}, '${rep}');` codeLines.push(line) } codeLines.push(' return text;'); codeLines.push('}\n\n'); let code = codeLines.join('\n'); // app.setClipboard(code); let d = Draft.create(); d.content = code; d.languageGrammar = "JavaScript"; d.update(); d.addTag("script"); d.addTag("regex"); d.update(); app.displayInfoMessage('function myRegExReplace() saved to Inbox :)') ; } function makeDraftActionCode(codeLines) { // Makes fully functioning draft Action Script: codeLines.push("var p = Prompt.create();"); codeLines.push("p.title = 'Run RegEx Replace on Current Draft';"); codeLines.push("p.addButton('Save as New Draft');"); codeLines.push("p.addButton('Replace Current Draft');"); codeLines.push(""); codeLines.push("if (p.show()) {"); codeLines.push(" let text = regExReplace(editor.getText());"); codeLines.push(" "); codeLines.push(" switch (p.buttonPressed) {"); codeLines.push(" case 'Save as New Draft':"); codeLines.push(" let d = Draft.create();"); codeLines.push(" d.content = text;"); codeLines.push(" // d.languageGrammar = 'MultiMarkdown';"); codeLines.push(" // d.languageGrammar = 'JavaScript';"); codeLines.push(" d.update();"); codeLines.push(" d.addTag('regexed');"); codeLines.push(" d.update();"); codeLines.push(" break;"); codeLines.push(" case 'Replace Current Draft':"); codeLines.push(" editor.setText(text);"); codeLines.push(" draft.addTag('regexed');"); codeLines.push(" break;"); codeLines.push(" }"); codeLines.push("} else {"); codeLines.push(" context.cancel('Canceled by user!');"); codeLines.push("}"); codeLines.push(""); } // Note! double escaping of \n \w etc: var groupSample = `StartIndex = 2 # Multiple to double line breaks /\\n\\n+/gm, "\\n\\n" # Multiple to single line breaks /\\n+/gm, "\\n" # Clean Markdown Lines /\\n+/gm, "\\n\\n" /\\n#/gm, "\\n\\n#" /(\\n###+.+?\\n)\\n/gm, "$1" /^([\\-+*>] .+?)\\n/gm, "$1" /^([\\-+*>] .+?\\n)(?![\\-+*>] )/gm, "$1\\n" # Smart quotes & m to n-dashes /"(.+?)"/gm, "“$1”" /(\\W)'(\\w.+?)'(\\W)/gm, "$1‘$2’$3" /(\\w)'/gm, "$1’" / ?-- ?/gm, " – " / ?— ?/gm, " – "`; // Run main function: main();
Options
-
After Success Default Notification Error Log Level Error
Items available in the Drafts Directory are uploaded by community members. Use appropriate caution reviewing downloaded items before use.