//Includes generic/enhanced variants of: //- RenameFromClipboard (http://resource.dopus.com/viewtopic.php?f=35&t=22867) //- Numbering of items supporting ".. of {total}" and others (http://resource.dopus.com/viewtopic.php?f=3&t=24534) // change log // ############################################################################ // v0.3.2 - 2025-12-25 // - fix script error, because "COMMANDS" option was missing after merging functionality from RenameFromClipboard // - turns out, even some more merging errors were present, sorry for that // v0.3.1 - 2025-09-09 // - use FSMagic v0.61 // - reorder change log // - import fix from RenameFromClipboard v0.3.1: fix FSMagic.getBasename() returning "" for files without extension // - import fix from RenameFromClipboard v0.3.1: add placeholder %item.new_name_stem% // v0.3 - 2015-10 // - new option "RECURSE", recursive rename for all items (native rename does not support this for folders) // v0.2 - 2015-05 // - new option "FROMCLIP", renders RenameFromClipboard command obsolete // - passing of pad-with changed to PAD="width=0" // - passing of pad-size changed to PAD="size=2" // v0.1.1 - 2015-05 // - unwanted output fixed // - padding error fixed // v0.1 - 2015-05 // - initial version // todos: // - something is odd when using files (not text) from clipboard as new filenames when renaming // todos taken from RenameFromClipboard (v0.3.1): // not sure what the status of these todos is, maybe they are "dids" already // - autodetecting for existence of fileextensions in the clip content // to not accidentally remove extensions on renamed files if clip content // contains only pure filenames and no extensions // - KEEPEXTension mode // ############################################################################ var template = //main features.. "NUMBER/O,"+ "FROM/K,TO/K,"+ "REMOVENUM/S,BY/K[<1>],PAD/O[size,with],"+ "PATTERN/K,"+ "NUMBERVARS/S["+ "{total} Total item count,"+ "{totalfiles} Total file count,"+ "{totaldirs} Total dir count,"+ "{totalsel} Selected item count,"+ "{totalselfiles} Selected file count,"+ "{totalseldirs} Selected dir count"+ "],"+ "PT/R,"+ //fromclip "FROMCLIP/O[quiet],"+ "SAFEMODE/S,"+ "COMMANDS/R," + //recurse "RECURSE/O,"+ "PATH/K,"+ //"PATTERN/K,"+ //"FROM/K,TO/K,"+ "TYPE/K[dirs,files],"+ "REGEXP/S,"+ //"SAFEMODE/S,"+ //shared misc "FILEINFO/S,NODESELECT/S,XLOG/O[off,xit,error,warning,info,,trace,dump,all]"; /////////////////////////////////////////////////////////////////////////////// function OnInit(data){ //uid added via script wizard (do not change after publishing this script) var uid = "641228BF-DBE7-4C09-BE38-6B229EDCB3A3"; //resource center url added via script wizard (required for updating) var url = "http://resource.dopus.com/viewtopic.php?f=35&t=24543"; data.name = "Command.Generic: RenameEx"; data.log_prefix = "RenameEx"; data.desc = "Extended Rename command."; data.copyright = "tbone"; data.version = "0.3.2"; data.min_version = "11.5"; data.default_enable = true; var cmd = data.AddCommand(); cmd.name = "RenameEx"; cmd.method = "Command_RenameEx"; cmd.desc = data.desc; cmd.label = "RenameEx"; cmd.template = template; /////////////////////////////////////////////////////////////////////////// function ConfigHelper(data){ //v1.2 var t=this; t.d=data; t.c=data.config; t.cd=DOpus.Create.Map(); t.add=function(name, val, des){ t.l={n:name,ln:name. toLowerCase()}; return t.val(val).des(des);} t.des=function(des){ if (!des) return t; if (t.cd.empty) t.d.config_desc=t.cd; t.cd(t.l.n)=des; return t;} t.val=function(val){ var l=t.l; if (l.v!==l.x&&typeof l.v=="object") l.v.push_back(val);else l.v=t.c[l.n]=val;return t;} t.trn=function(){return t.des(t("script.config."+t.l.ln));}} /////////////////////////////////////////////////////////////////////////// var cfg = new ConfigHelper(data); cfg.add('XLog', DOpus.Create.Vector()). val(5).val("Off").val("Exception").val("Error").val("Warning").val("Info").val("Normal").val("Trace").val("Dump"). des('Console output level. The higher, the more output will be visible in the console window. '); } /////////////////////////////////////////////////////////////////////////////// var COMMAND_FAILURE = true; var COMMAND_USERABORT = true; var COMMAND_BADPARAMS = true; var COMMAND_SUCCESS = false; /////////////////////////////////////////////////////////////////////////////// String.prototype.lTrim = function(chr){chr=chr||"\\s";return new String(this.replace(new RegExp("^"+chr+"*"),''));} String.prototype.rTrim = function(chr){chr=chr||"\\s";return new String(this.replace(new RegExp(chr+"*$"),''));} String.prototype.trim = function(chr){return this.lTrim(chr).rTrim(chr);} String.prototype.left = function(chrs){return this.substring(0,chrs);} String.prototype.right = function(chrs){return this.substring(this.length-chrs);} /////////////////////////////////////////////////////////////////////////////// function Command_RenameEx(data){ var args = ArgsMagic(data, null, template), result = null; if (args["XLOG"].exists) XLog = args["XLOG"].value; Log("CmdLine: " + data.cmdline,"D"); if (args["NODESELECT"].exists) data.func.command.deselect = false; if (0); else if (args["NUMBER"].exists) return RenameEx_NUMBER(data.func.command, data.func.sourcetab, args); else if (args["FROMCLIP"].exists) return RenameEx_FROMCLIP(data.func.command, data.func.sourcetab, args); else if (args["RECURSE"].exists) return RenameEx_RECURSE(data.func.command, data.func.sourcetab, args); if (result===null){ Log("Bad/incomplete params.","X"); return COMMAND_BADPARAMS; } return result; } /////////////////////////////////////////////////////////////////////////////// function RenameEx_RECURSE(cmd, sourcetab, args) { Log("RenameEx_RECURSE():","T",1); /////////////////////////////////////////////////////////////////////////// function GetFolderItemsRecursiveSortedByDeepness(folderEnum){ var items = []; while (!folderEnum.complete){ var item = folderEnum.next(); var path = item.realpath+""; //remove drive letter or unc location from item path path = path.replace( /(.:\\|\\\\.*?\\.*\\)(.*)/g, "$2"); //split by "\" to determine deepness var level = path.split("\\").length; items[items.length] = {level:level, item:item }; } //sort items by level (path deepness), deepest items first //this is because renaming directories "at the top" makes it hard to rename //items further below, as their path gets invalid when renaming their container/parent items.sort(function(a,b) { if (a.level < b.level) return 1; if (a.level > b.level) return -1; return 0; }); return items; } /////////////////////////////////////////////////////////////////////////// var fm = new FSMagic(); var sourcePath = sourcetab.path+""; if (sourcetab.selected_dirs.count) sourcePath = sourcetab.selected_dirs(0).realpath; if (args["PATH"].exists) sourcePath = args["PATH"].value; Log("SAFEMODE : " + args["SAFEMODE"].exists,"D"); Log("PATH : " + sourcePath,"D"); var folderEnum = fm.ReadFolder(sourcePath, true, false); if (!folderEnum){ Log("Error reading folder ["+sourcePath+"].", "E"); return COMMAND_FAILURE; } var items = GetFolderItemsRecursiveSortedByDeepness(folderEnum); cmd.ClearFiles(); for(var i=0;iselItems.count || selItems.enumerator.atEnd()) break; var selItem = selItems.enumerator.item(); selItems.enumerator.moveNext(); var newItem = DOpus.FSUtil.NewPath(newItems[c]); for(var ce=0;ce padding) return value; len = padding-len+1; return (padding?new Array(len).join(padStr)+value:value); } /////////////////////////////////////////////////////////////////////////// function ReplaceVars(str,vars,padding,padWith){ return ""+ str.replace('{total}', Pad(vars.total, padding, padWith)). replace('{totalfiles}', Pad(vars.totalfiles, padding, padWith)). replace('{totaldirs}', Pad(vars.totaldirs, padding, padWith)). replace('{totalsel}', Pad(vars.totalsel, padding, padWith)). replace('{totalselfiles}', Pad(vars.totalselfiles, padding, padWith)). replace('{totalseldirs}', Pad(vars.totalseldirs, padding, padWith)); } /////////////////////////////////////////////////////////////////////////// var vars = { total : sourcetab.all.count, totalfiles : sourcetab.files.count, totaldirs : sourcetab.dirs.count, totalsel : sourcetab.selected.count, totalselfiles : sourcetab.selected_files.count, totalseldirs : sourcetab.selected_dirs.count } /////////////////////////////////////////////////////////////////////////// var padding = args["PAD"].exists ? "auto" : "0"; var padding = args["PAD"].options["size"]!=undefined ? args["PAD"].options["size"] : padding; var padWith = args["PAD"].options["with"]!=undefined ? args["PAD"].options["with"] : "0"; var to = args["TO"].exists ? args["TO"].value : "{stem} {num}{ext}"; var pattern = args["PATTERN"].exists ? args["PATTERN"].value : "(.*?)((?:[^A-z0-9]*|\\s*)?\\D?\\d+.*?)?($|\\..*)"; padding = ((""+padding).toLowerCase()=="auto"?(""+vars.totalsel).length:padding)*1; //remove native NUMBER switch from commandline, as it makes the rename //malfunction if no numbering shall take place (just adding {total..} e.g.) if (to.indexOf("{num}")==-1){ args["NUMBER"].exists = false; args["NUMBER"].value = ""; } Log("NUMBER : " + args["NUMBER"].value,"D"); Log("PATTERN : " + pattern,"D"); Log("FROM : " + args["FROM"].value,"D"); Log("TO : " + to,"D"); Log("PAD size: " + padding,"D"); Log("PAD with: " + padWith,"D"); var cmdline = 'Rename PATTERN="'+pattern+'" '+ ('TO="'+ReplaceVars(to, vars, padding, padWith)+'" '). replace('{stem}','\\1'+(!args["REMOVENUM"].exists?'\\2':'')). replace('{num}','[#'+padding+']'). replace('{ext}','\\3')+ 'REGEXP'+ (args["NUMBER"].exists && args["NUMBER"].value==""?' NUMBER':'')+ (args["NUMBER"].exists && args["NUMBER"].value!=""?' NUMBER="'+args["NUMBER"].value+'"':'')+ (args["FILEINFO"].exists?' FILEINFO':'')+ (args["FROM"].exists?' FROM="'+args["FROM"].value+'"':'')+ (args["BY"].exists?' BY="'+args["BY"].value+'"':'')+ (args["PT"].exists?' '+args["PT"].value:''); Log("Cmdline: " + cmdline,"D"); cmd.RunCommand(cmdline); Log("-","T",-1); return COMMAND_SUCCESS; } // ############################################################################ // # prototypes // ############################################################################ String.prototype.getFilePart = function (){ return this.split("\\").slice(-1)[0]; } // ############################################################################ String.prototype.getParentPart = function (){ var arr = this.split("\\"); arr.pop(); //remove last element return arr.join("\\"); } // ############################################################################ String.prototype.getExtension = function (){ return this.getFilePart().split(".").slice(-1)[0]; } // ############################################################################ String.prototype.getBaseName = function (){ var arr = this.getFilePart().split("."); if (arr.length==1) return arr[0]; arr.pop(); return arr.join("."); } /////////////////////////////////////////////////////////////////////////////// function TextMagic(){ this.version = 0.1; /////////////////////////////////////////////////////////////////////////// this.TextToFilePaths = function( text ){ var lineEnding = this.GetLineEndingOfText(text); if (lineEnding!=""){ var itemNames = text.split(lineEnding); } else var itemNames = new Array(text); return this.CleanFilePaths(itemNames) } /////////////////////////////////////////////////////////////////////////// this.TextToDOItems = function( text ){ var items = []; var filePaths = this.TextToFilePaths(text); for(var i=0;i"); if (parts.length>1) opValue = parts.slice(1).join(""); this.options[opName] = opValue; } if (!(!this.value && subOpsTemplate)) return; if (!(match = subOpsTemplate.match(/\<(.+?)\>/))) return; this.options[match[1]] = true; this.value = match[1]; } /////////////////////////////////////////////////////////////////////////// var types = {s:"switch",k:"keyword",o:"optional",n:"numeric",m:"multiple",r:"raw"}; var names = template.replace(/\/(.)\,/g,"/$1;").replace(/\]\,/g,"];").split(";"), args = []; for(arg in names) { var parts = names[arg].split("/"); var name=parts[0].toUpperCase(), subOpsTemplate=null; var parts2 = parts[1].split("["); var type=parts2[0].toLowerCase(); if (parts2.length>1) subOpsTemplate = parts2[1].substring(0,parts2[1].length-1); var exists=(data&&data.func.args.got_arg[name])||(jsonData&&(jsonData[name]!=undefined))||false; var value = (data && data.func.args[name]) || (jsonData && jsonData[name]) || undefined; args.push( args[name] = new Arg(name, types[type], exists, value, subOpsTemplate )); } /////////////////////////////////////////////////////////////////////////// args.Get = function( argName, configName, noneValue){ var arg = this[argName = argName.toUpperCase()]; if (!arg) throw new Error(0, "Unknown parameter ["+argName+"]"); if (arg.exists) return arg.value; //DOpus.Output("argName: " + argName); //DOpus.Output("configName: " + configName); if (!configName) return noneValue; if (typeof Script.config[configName] == "string") return new String(""+Script.config[configName]); return Script.config[configName]; } return args; } /////////////////////////////////////////////////////////////////////////////// function DumpObject(o, name, upperCaseProps, self, level){ this.version = 0.4; name = name || "unnamed"; if (self == undefined) DOpus.Output("DumpObject(): ["+name+"]"); var maxLen = 0; upperCaseProps = upperCaseProps || false; self = self || false; level = level || " "; for(prop in o) if (prop.length > maxLen) maxLen = prop.length; for(prop in o) { var pad = ""; while (pad.length+prop.length