// // DIR-FDOUBLE.VDM Christian Ziemski 09.03.2004 // 19.03.2004 // // Helper tool for finding double files per name in a directory tree. // // Only for WINNT and above yet (due to DIR format) // (And only tested with German Windows yet.) // // // //----- Online documentation (via Shift-F1) between tags and below --- // //----- Please note: Due to the proportional font of DI_1() it is necessary // to use manual TAB's in the following documentation block! // (And here and there a space for finetuning...) // // DIR-FDOUBLE Helper tool for finding double files (C) Ch.Ziemski 3/2004 // // This macro lists all files with double filenames found in a directory tree. // The following data is shown for each file: name, date, time, size and path. // // Provides these commands via key assignment to work with the files: // // F12 - open file at cursor line // - close the file again // // Shft-F12 - open files with same name in and around cursor line // - closes all same named files again // // Ctrl-F12 compare files with same name in and around cursor line // (And marks them with A..J to show diffs) // // Ctrl-F10 - toggle DELETE flag of file in current cursor line // - toggle DELETE flag of current file in list // Ctrl-Shft-F10 ... and advance one line // // Alt-F10 delete non double filenames // // F11 sort list by filename // Shft-F11 sort list by path // Ctrl-F11 sort list by DELETE flag (a '#') at end of file // Alt-F11 sort list by file size // // // Shft-F1 this help //-- // // // This macro is still under development. // // If you have any comments, please let me know. // // Christian // // // //---------------------------------------------------------------------------------- // // To do: // // - test and support more Windows versions // - input parameter for drive/path as alternative for DI_1() // - more comfort and tools // - Tools menu additional to the key assignments // - UNC pathnames? how to check Cur_Col then? // - Win_Split()/Win_Create() error handling // - generally more error handling // - clean up and document register usage // - sub titles for paragraphs (//--) like in DI1-PAGER // - Config(CM_E_UNDO,0) doesn't always work!? // //-------------------------------------------------------------------------- // // #90 reg num of running macro // #91 file list loaded into a free buffer // #92 win_width // #93 win_height // #94 flag for formatting empty lines in filename-sort // // #103 // #104 // #105 // // @103 // @104 // @105 // @106 //-------------------------------------------------------------------------- if ( ! Is_Windows) { Statline_Message("Sorry, this macro requires VEDIT for Windows.") return } #94=1 // insert an empty line after each filename-block in name sort Key_Add("Shft-F1" , "[VISUAL EXIT] Call(#90, 'HELP')",OK) Key_Add("F11" , "[VISUAL EXIT] Call(#90, 'SORTBYNAME') if (#94){Call(#90, 'INSERTDIVIDER')}", OK) Key_Add("Shft-F11", "[VISUAL EXIT] Call(#90, 'SORTBYPATH')", OK) Key_Add("Ctrl-F11", "[VISUAL EXIT] Call(#90, 'SORTBYFLAG')", OK) Key_Add("Alt-F11" , "[VISUAL EXIT] Call(#90, 'SORTBYSIZE')", OK) Key_Add("F12" , "[VISUAL EXIT] Call(#90, 'OPENFILE')", OK) Key_Add("Shft-F12", "[VISUAL EXIT] Call(#90, 'OPENFILES')", OK) Key_Add("Ctrl-F12", "[VISUAL EXIT] Call(#90, 'COMPAREFILES')", OK) Key_Add("Ctrl-F10" , "[VISUAL EXIT] #104=0 Call(#90, 'TOGGLEDEL')", OK) Key_Add("Ctrl-Shft-F10", "[VISUAL EXIT] #104=1 Call(#90, 'TOGGLEDEL')", OK) Key_Add("Alt-F10" , "[VISUAL EXIT] Call(#90, 'SORTBYNAME') Call(#90, 'DELNONDOUBLE') if (#94){Call(#90, 'INSERTDIVIDER')}", OK) #103=File_Check("|(VEDIT_TEMP)/dir.out") if (#103 != -1) { Buf_Switch(#103) Buf_Quit(OK) Update } #90 = Macro_Num //#90 = reg# of running macro Reg_Set(103, HOME) // default directory name Call("GETDIR") Config( D_H_CR_LINE, "Highlight cursor line (1=Full, 2=Partial)", 1 ) if (File_Size > 0) { Buf_Switch(Buf_Free) } #91=Buf_Num #92=Win_Width #93=Win_Height Reg_Set(104, "|(VEDIT_TEMP)/dir.out") // save into this target file Message("getting directory listing...", STATLINE) Call("SHELLOUT") // Shell out with "dir" command to create DIR.OUT Buf_Switch(#91) File_Open("|(VEDIT_TEMP)/dir.out",NOEVENT) Update Call("MAKEDIRLIST") // Clean up "dir" output Update Call("LINEUP") // format the list with equally sized file names Update Call("SORTBYNAME") // sort list according file names Update Call("DELNONDOUBLE") // delete lines with single files Update if (#94) { Call("INSERTDIVIDER") } return ///////////////////////////////////////////////////////////////////////////// // // SUBROUTINES // //--------------------------------------------------------------------------- // // LINEUP // :LINEUP: Message("formatting directory listing...", STATLINE) #103=0 BoF while (! At_EOF) { Search("::", NOERR+ERRBREAK) #103 = Max(#103, Cur_Col) Line(1, NOERR) } BoF while (! At_EOF) { Search("::", NOERR+ERRBREAK) Del_Char(2) Ins_Char(32, COUNT, #103-Cur_Col+4) } return // -------------------------------------------------------------------------- // // DELNONDOUBLE // :DELNONDOUBLE: // delete lines with non double filenames in a sorted text Message("extracting doubles ...", STATLINE) BoF Search(" |A:\", NOERR) if (EM) { return } BB(CLEAR) #103=Cur_Col-42 // a character before the date column BoF RCB(104, BoL_Pos, BoL_Pos+#103) Line(1, NOERR) #104=1 repeat (ALL) { if (Match(@104)==0) { #104++ } else { if (#104==1) { Del_Line(-1) } else { #104=1 } RCB(104, BoL_Pos, BoL_Pos+#103) } Line(1, NOERR+ERRBREAK) } BoF return // -------------------------------------------------------------------------- // // // SHELLOUT - Shell out, using directory name in @103 and target filename in @104 // With long filenames, need to shell out with command such as: // dir "filespec" /one // Else, shell out with command such as: // dir filespec /one // :SHELLOUT: if (Is_Longfilename) { //Need double-quotes around filespec Reg_Set(105,'dir "') Reg_Set(105,@103, APPEND) Reg_Set(105,'"', APPEND) } else { Reg_Set(105,'dir ') Reg_Set(105,@103, APPEND) } Reg_Set(105,' /s', APPEND) // need the "/s" option Reg_Set(105,' /one >', APPEND) // Sort by name and then extension // // Add redirection filename; long filename needs double-quotes // if (Is_Longfilename) { //Need double-quotes around filespec Reg_Set(105,' "', APPEND) } Reg_Set(105, @104, APPEND) if (Is_Longfilename) { //Need double-quotes around filespec Reg_Set(105,'"', APPEND) } // // Shell out with "dir" command to create a sorted list of files: // The syntax with single AND double quotes is necessary because of // long filenames (strange but true) ... // The difference between NT/2000/XP and W9x must be handled carefully! // if (Is_WinNT) { Sys('"|@(105)"',DOS+SIMPLE+OK+SUPPRESS) } else { Sys('|@(105)',DOS+SIMPLE+OK+SUPPRESS) } return() // -------------------------------------------------------------------------- // // MAKEDIRLIST - removes: - header and footer // - subdirectory entries // from DIR output (in English and German Windows) // :MAKEDIRLIST: Message("processing directory listing...", STATLINE) BOF() Search("|{directory|wof,Verzeichnis|wvon}|W",ADVANCE+NOERR) BOL() Del_Line(-ALL) //Delete Header info Replace("^.*.*\N", "", REGEXP+BEGIN+ALL+CASE+NOERR) BOF() repeat (ALL) { Search("|<|{Total Files Listed:,Dateien gesamt:}",ERRBREAK) Del_Line(0); Del_Line(1) } Replace("|< |Y|N","",BEGIN+ALL+NOERR) //Delete info lines beginning with spaces Replace("|<|N","", BEGIN+ALL+NOERR) //Delete empty lines // // Now do the subdirs // BOF() //loop through every directory block Search("|{directory|Wof,Verzeichnis|Wvon}|W",ADVANCE+NOERR) while (!Error_Match) { Reg_Copy_Block(17,Cur_Pos, EoL_Pos) //complete pathname BOL() Del_Line(1) //delete info line if (At_EOF) { Break } while (Match("|[|W]|{directory|Wof|W,verzeichnis|Wvon|W}") != 0) { Goto_Col(40) // ? Reg_Copy_Block(18, Cur_Pos, EoL_Pos) Ins_Text(" ") Reg_Ins(17) Ins_Text("\") BoL Reg_Ins(18) Ins_Text(" :: ") Line(1,NOERR+ERRBREAK) if (At_EoF) { break } } Char(Chars_Matched) if (At_EOF) { Break } } return // -------------------------------------------------------------------------- // // OPENFILE - opens the file in the current cursor line // (if in buffer with file list; // else close current file and switch to list) :OPENFILE: if (Buf_Num == #91) { Save_Pos() Search_Block(" |A:\", BoL_Pos, EoL_Pos, NOERR) if (EM) { Restore_Pos() return } Reg_Copy_Block(106, Cur_Pos+1, EoL_Pos) Restore_Pos() File_Open("|@(106)") } else { Buf_Quit(DELETE) Buf_Switch(#91) // Buf_Switch(#91, ATTACH) // Win_Switch(Win_Next(BUFFER)) // ??? // #77=Win_Num // Win_Move(Win_Num,0,0,#92, #93) // geht so nicht!? Falsches Window wird vergrössert. // Win_Move(#91,0,0,#92, #93) // unsauber } return // -------------------------------------------------------------------------- // // OPENFILES - opens the files with the same filename like the file in the // current cursor line (list must be sorted by filename). // (if in buffer with file list; // else try to close current file and all with that name // and then switch to list) // :OPENFILES: if (Buf_Num == #91) { Save_Pos() Search_Block(" |A:\", BoL_Pos, EoL_Pos, NOERR) if (EM) { Restore_Pos() return } Char(-42) #103=Cur_Col // a character before the date column Search("|!|W", REVERSE+ADVANCE) Reg_Copy_Block(105, BoL_Pos, Cur_Pos) // filename Line(-1, NOERR) if (! EM) { while (Match(@105)==0) { Line(-1, NOERR+ERRBREAK) } if (! At_BoF) { Line(1) } } #104=-1 // # of first new opened buffer Block_Begin(Cur_Pos) while (Match(@105)==0) { Goto_Col(#103+43) Reg_Copy_Block(106, Cur_Pos, EoL_Pos) if (File_Check(@106) == -1) { #105=Buf_Free if (#104==-1) { //Win_Split(#105, Win_Lines-10, BOTTOM) Win_Move(Win_Num, Win_X_Org, Win_Y_Org, Win_Width, 10*Font_Height) Win_Create(#105, Win_Y_Org+10*Font_Height, Win_X_Org, #93-10*Font_Height, #92, PIXEL) } else { Win_Split(#105, 0, RIGHT) // problematic if not enough screen width } Buf_Switch(#105) } File_Open("|@(106)") if (#104==-1) { #104=Buf_Num } Win_Attach(#105) Win_Switch(#105) Update // why necessary? Buf_Switch(#91) Line(1, NOERR+ERRBREAK) } Block_End(Cur_Pos) Restore_Pos() Set_Visual_Line(0) Buf_Switch(#104) } else { Reg_Set(103, FILENAME) if (Reg_Size(103)>0) { #103=1 do { Buf_Switch(#103) if (#103 != #91) { if (Buf_Status(#103) > -1) { Reg_Set(104, FILENAME) if (Reg_Compare(103, @(104)) == 0) { Buf_Quit(DELETE) // incl. delete attached windows } } } #104=#103 #103 = Buf_Next } while(#103 > #104) } Buf_Switch(#91) //Win_Move(Win_Num,0,0,(Screen_Cols+1)*Font_Width, (Screen_Lines+1)*Font_Height) Win_Move(Win_Num,0,0,#92, #93) } return // -------------------------------------------------------------------------- // // SORTBYNAME - sort the list by filename // :SORTBYNAME: if (Buf_Num == #91) { Message("sorting ...", STATLINE) Replace("|<|[|W]|N", "", BEGIN+ALL+NOERR) // remove empty lines BoF Search(" |A:\", NOERR) if (EM) { return } BB(CLEAR) #103=Cur_Col-42 // a character before the date column Sort_Merge("1,#103", 0, FileSize) BoF } return // -------------------------------------------------------------------------- // // SORTBYPATH - sort the list by pathname // :SORTBYPATH: if (Buf_Num == #91) { Message("sorting ...", STATLINE) Replace("|<|[|W]|N", "", BEGIN+ALL+NOERR) // remove empty lines Message("Processing... (About 30 sec / Megabyte) -- Press to abort ",STATLINE) #104=0 Begin_Of_File() while (! AT_EOF) { EOL() #104=Max(#104, Cur_Col) Line(1,ERRBREAK) } BoF Search(" |A:\", NOERR) if (EM) { return } BB(CLEAR) Sort_Merge("Cur_Col+1,#104", 0, FileSize) BoF } return // -------------------------------------------------------------------------- // // SORTBYSIZE - sort the list by filesize // :SORTBYSIZE: if (Buf_Num == #91) { Message("sorting ...", STATLINE) Replace("|<|[|W]|N", "", BEGIN+ALL+NOERR) // remove empty lines BoF Search(" |A:\", NOERR) if (EM) { return } BB(CLEAR) Search("|!|W", REVERSE) #104=Cur_Col+1 Search("|W", REVERSE) Search("|!|W", REVERSE) #103=Cur_Col+1 Sort_Merge("#103,#104", 0, FileSize) BoF } return // -------------------------------------------------------------------------- // // SORTBYFLAG - sort the list by flag and pathname // :SORTBYFLAG: if (Buf_Num == #91) { Message("sorting ...", STATLINE) Replace("|<|[|W]|N", "", BEGIN+ALL+NOERR) // remove empty lines Message("Processing... (About 30 sec / Megabyte) -- Press to abort ",STATLINE) #104=0 Begin_Of_File() while (! AT_EOF) { EOL() #104=Max(#104, Cur_Col) Line(1,ERRBREAK) } BoF Search(" |A:\", NOERR) if (EM) { return } BB(CLEAR) Sort_Merge("Cur_Col-1,#104", 0, FileSize) EoF } return // -------------------------------------------------------------------------- // // INSERTDIVIDER - inserts an empty line after each filename-group // :INSERTDIVIDER: Replace("|<|[|W]|N", "", BEGIN+ALL+NOERR) // remove empty lines BoF Search_Block(" |A:\", BoL_Pos, EoL_Pos, NOERR) if (EM) { return } Char(-42) #103=Cur_Col // a character before the date column while (! At_EoF) { Search("|!|W", REVERSE+ADVANCE) Reg_Copy_Block(105, BoL_Pos, Cur_Pos) // filename BoL while (Match("|@(105) ") == 0) { Line(1, NOERR+ERRBREAK) } Ins_Newline(1) Goto_Col(#103) } BoF return // -------------------------------------------------------------------------- // // TOGGLEDEL - toggle the DELETE-Flag // if in list: toggle file in cursor line // if in open file: toggle that file in list // :TOGGLEDEL: if (Buf_Num == #91) { Save_Pos() BoL Search(" |A:\", NOERR) if (EM) { Restore_Pos() return } Char(-1) if (Match("#") == 0) { Ins_Char(32, OVERWRITE) } else { Ins_Char(35, OVERWRITE) } Restore_Pos() if(#104==1) { Do_Visual("\CD\") } } else { #103=Buf_Num Reg_Set(103, PATHNAME) Buf_Switch(#91) Search(@103, BEGIN+NOERR) if (EM) { Buf_Switch(#103) return } Char(-2) if (Match("#") == 0) { Ins_Char(32, OVERWRITE) } else { Ins_Char(35, OVERWRITE) } Buf_Switch(#103) } return // -------------------------------------------------------------------------- // // GETDIR - user input dialog for the directory to be checked // // to do: more than 10 files :GETDIR: Reg_Set(104, "Please enter the directory to be checked (including it's subdirectories)") repeat (ALL) { //LOOP until valid directory #103=Dialog_Input_1(103,"`Double File Checker`, `|@(104) `, `??`,`[&OK]`, `[&Help]`, `[&Cancel]`", @103,APP+CENTER,0,0) if ((#103 == 0) || (#103 > 2)) { Break_Out(EXTRA) } if (#103 == 2) { Call("HELP") Continue } Reg_Set(104,"Directory invalid or doesn't exist. Try again.") if (Reg_Size(103) < 2) { continue } if (File_Exist(@103,NOERR) != 0) { Break } } return() // -------------------------------------------------------------------------- // // COMPAREFILES // :COMPAREFILES: if (Buf_Num == #91) { Save_Pos() Search_Block(" |A:\", BoL_Pos, EoL_Pos, NOERR) if (EM) { Restore_Pos() return } Char(-42) #103=Cur_Col // a character before the date column Search("|!|W", REVERSE+ADVANCE) Reg_Copy_Block(105, BoL_Pos, Cur_Pos) // filename Line(-1, NOERR) if (! EM) { while (Match(@105)==0) { Line(-1, NOERR+ERRBREAK) } if (! At_BoF) { Line(1) } } #97=70 // number of same files + 69 while (Match(@105)==0) { Goto_Col(#103+43) Reg_Copy_Block(106, Cur_Pos, EoL_Pos) File_Open("|@(106)", NOEVENT) #@97=Buf_Num // save buffer numbers of opened files in #70-#79 // to do: mark already open files with +1000 or so Buf_Switch(#91) Line(1, NOERR+ERRBREAK) #97++ if (#97 > 79) { // only 10 files yet break } } #97-- // max reg. if (#97 == 70) { // if only one file Restore_Pos() return } for (#104=80; #104<=(#97+10); #104++) { #@104=#104-80 // set #80-#89 to 0..9 } #98=70 #99=71 repeat (ALL) { repeat (ALL) { #95=#@98 #96=#@99 // .#98 #99 Buf_Switch(#95) BoF #106=File_Size Buf_Switch(#96) BoF if (#106 == File_Size) { // if same size if (Compare(#95+BUFFER, CASE) == 0) { // if at least one character matched #106=0 if (!At_EoF) { // not all identical #106=1 } } else { #106=1 } } else { #106=1 } if (#106 == 0) { // if identical #104=#99+10 #105=#98+10 #@104=#@105 // set to 0..9 from 1.st field } #99++ // 2nd pointer++ if (#99 > #97) { #99=#98+2 break } } #98++ // 1st pointer++ if (#98 == #97) { break } } for (#104=70; #104<=#97; #104++) { // mark in list and close all compared files // to do: let opened files open Buf_Switch(#@104) Reg_Set(104, PATHNAME) Buf_Switch(#91) Search(@104, BEGIN) Char(-3) #105=#104+10 Ins_Char(#@105+65, OVERWRITE) Buf_Switch(#@104) Buf_Quit(OK) } Restore_Pos() Buf_Switch(#91) } return // -------------------------------------------------------------------------- // HELP - Show some help instructions (the inline doco from top of the macro) // // Used register (restored at end) // // #103 longest line // button number // #104 longest paragraph // #105 tmp: line num // #106 original buffer num // #107 counter for paragraph // // @103 title line for DI_1() // @104 button label // @105 text of one paragraph // @106 line of dashes // :HELP: Num_Push(103,107) // save used registers Reg_Push(103,107) Key_Purge() #106=Buf_Num Buf_Switch(Buf_Free(EXTRA)) Reg_Ins(Macro_Num) Config(CM_E_UNDO,0) // doesn't always work!? BoF Search("//|[|W]|H3CDOC>", NOERR+ADVANCE) // the leading "<" is masked out intentionally! if(EM){ Message("\n\n No inline help found.", STATLINE) }else{ Reg_Copy_Block(103, Cur_Pos, EoL_Pos) Line(1) Del_Line(-ALL) Search("//|[|W]|H3C/DOC>", NOERR) if(!EM){ BoL Del_Block(Cur_Pos, File_Size) } Replace("^/+", BEGIN+ALL+REGEXP+NOERR) // remove leading comment characters BoF while(! At_EoF){ // shorten lines longer than, say 100 (must be improved!!!!, perhaps wrapping?) Goto_Col(100) Del_Block(Cur_Pos, EoL_Pos) Line(1, NOERR+ERRBREAK) } } // For security; to prevent DI1() from beeing killed - but not foolproof! Replace("`","'", BEGIN+ALL+NOERR) Replace("||","!", BEGIN+ALL+NOERR) // find longest line #103=0 BoF while (! At_EoF) { EoL #103=Max(#103, Cur_Col) Line(1, NOERR) } // Create a line of underscores of (hopefully) appropriate length // to size the dialog's width. // (Due to the used proportional font it's only guessing!) BoF Ins_Char(95, COUNT, Max(37,#103)) Ins_Newline(1) BoF Reg_Copy(106, 1) Del_Line(1) // find longest "paragraph" ==> #104 BoF #104=0 #105=1 while (! At_EoF) { Search("|<--", NOERR) if (EM) { EoF } #104=Max(#104, Cur_Line - #105) Line(1, NOERR+ERRBREAK) #105=Cur_Line } // make all "paragraphs" equal length BoF #105=1 while (! At_EoF) { Search("|<--", NOERR) if (EM) { EoF } else { BoL Del_Line(1) } if ((Cur_Line - #105 + At_EoF) < #104) { Ins_Newline(#104 - Cur_Line + #105) } #105=Cur_Line } BoF // try to format via TABs and change TAB's to \t for DI_1() Retab_Block(0, Filesize) Replace("|009", "\t", BEGIN+ALL+NOERR) BoF Key_Purge() // scroll through the text Reg_Set(104, "&Next") // [Next/End] button button label Reg_Set(107, "[( Previous )]") // [Previous] button label #107=1 // counter for paragraph while (! At_EoF) { Reg_Copy(105,#104) #105=Cur_Line Line(#104, NOERR) if (At_EoF) { // change the button label if at EoF Reg_Set(104, "E&nd") } #105=Cur_Line - #105 // remember the number of lines gone forward #103=Dialog_Input_1(103,^`|@(103)`, `|@(105)`, `|@(106)`, `|@(107)`,`.b[|@(104)]`,`[&Cancel]`^,SET+APP+CENTER,0,0) if (#103 == 1) { // [Previous] Reg_Set(104, "&Next") #107-- Line(-#104-#105, NOERR) if (#107<=1) { #107=1 Reg_Set(107, "[( Previous )]") // button label } continue } if (#103 != 2) { // not [Next] break } #107++ Reg_Set(107, "[&Previous]") // button label } Buf_Quit(OK) Buf_Switch(#106) Reg_Pop(103,107) // restore used registers Num_Pop(103,107) Key_Purge() return