AutoHotkey Homepage AutoHotkey Community
Let's help each other out
 
 FAQFAQ   SearchSearch   MemberlistMemberlist   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

Function: IncrementName (create next file)

 
Post new topic   Reply to topic    AutoHotkey Community Forum Index -> Scripts & Functions
View previous topic :: View next topic  
Author Message
PhiLho



Joined: 27 Dec 2005
Posts: 6721
Location: France (near Paris)

PostPosted: Tue Feb 28, 2006 5:04 pm    Post subject: Function: IncrementName (create next file) Reply with quote

I wrote a little function to increment the number at the end of a filename (without extension, already removed with FileSplitPath).
I thought I could share with everybody which may need something similar:
Code:
IncrementName(nameNoExt)
{
   local d, dd

   Loop
   {
      ; Take out rightmost char (digit?)
      StringRight d, nameNoExt, 1
      StringTrimRight nameNoExt, nameNoExt, 1
      If d = 9
      {
         ; Propagate carry: continue
         dd = 0%dd%
      }
      Else If d between 0 and 8
      {
         ; Just increment this digit
         dd := (d + 1) . dd
         Break
      }
      Else
      {
         ; Not a digit: put back non digit char, add a 1
         nameNoExt = %nameNoExt%%d%1
         Break
      }
   }
   Return nameNoExt . dd
}

Test code:
Code:
r := r . IncrementName("foo") . "`n"
r := r . IncrementName("foo0") . "`n"
r := r . IncrementName("foo5") . "`n"
r := r . IncrementName("foo9") . "`n"
r := r . IncrementName("foo10") . "`n"
r := r . IncrementName("foo15") . "`n"
r := r . IncrementName("foo19") . "`n"
r := r . IncrementName("foo111") . "`n"
r := r . IncrementName("foo199") . "`n"
r := r . IncrementName("foo999") . "`n"

r := r . IncrementName("foo000") . "`n"
r := r . IncrementName("foo005") . "`n"
r := r . IncrementName("foo009") . "`n"
r := r . IncrementName("foo010") . "`n"
r := r . IncrementName("foo015") . "`n"
r := r . IncrementName("foo019") . "`n"
MsgBox %r%

[EDIT] I integrated partly evl's and Titan' suggestions of improvement.
_________________
vPhiLho := RegExReplace("Philippe Lhoste", "^(\w{3})\w*\s+\b(\w{3})\w*$", "$1$2")


Last edited by PhiLho on Wed Mar 01, 2006 9:55 am; edited 3 times in total
Back to top
View user's profile Send private message Visit poster's website
PhiLho



Joined: 27 Dec 2005
Posts: 6721
Location: France (near Paris)

PostPosted: Tue Feb 28, 2006 5:29 pm    Post subject: Reply with quote

While writing this little function, I made a number of big errors, making me wonder what was going on...
Instead of keeping my shame for me, I thought I should also share my errors, as seeing them may help some people to avoid these traps...
Here is the erroneous function:
Code:
debugF = debug.txt
; Same test code

IncrementName(nameNoExt)
{
   MsgBox => %nameNoExt%`n%debugF%
   FileAppend => %nameNoExt%`n, %debugF%
   Loop
   {
      StringRight d, nameNoExt, 1
      StringTrimRight nameNoExt, nameNoExt, 1
      FileAppend %A_Index% - %d%`n, %debugF%
      If d between 0 and 8
      {
         d++
         dd = %d%%dd%
         FileAppend %A_Index% - %dd%`n, %debugF%
         Break
      }
      Else If (d = 9)
      {
         dd = 0%dd%
         FileAppend %A_Index% - %dd%`n, %debugF%
      }
      Else
      {
         ; Not a digit
         nameNoExt = %nameNoExt%%d% ; put back non digit char
         ; Must add a 1
         nameNoExt = %nameNoExt%1
         Break
      }
   }
   FileAppend => %nameNoExt%%dd%`n, %debugF%
   Return %nameNoExt%%dd%
}

As you can see, since the MsgBox returned empty strings, I added some internal MsgBox and FileAppend to see what didn't worked...
I was stumped to see neither messages nor new file...
Do you know what were my errors (you can guess by comparing with the correct code, so don't look right now). There are three big mistakes.

.
.
.

First error: I still get bitten by the PHP-like scope of global variables.
I forgot to put a global or local d, dd declaration on top of the function. So debugF isn't visible and the FileAppend silently fails to append to an empty filename.

Second error: I like to omit the initial comma, it gives a Visual Basic flavor... I knew that the initial comma must be present if the first parameter is omitted (a quite rare case). But I fall into a not so obvious trap: MsgBox =xxx and FileAppend =xxx are really assignments of strings to variables having the same name as the commands... So they did nothing... Bad choice of debug string prefix!

Third error: I knew that Return var and Return %var% are synonym. I am not sure where I got this information as it is not documented in the Return page... Probably seen it in a script.
Anyway, Return argument must actually be an expression, except in the above exception. I wrote Return %nameNoExt%%dd% which actually built a variable name corresponding to the concatenation of the strings, and returned the value of the variable, which was empty of course.

Note to Chris: the error message box showing lines from the faulty script have a curious problem that make me wonder if my function, returning empty string, was actually called.
For example:
Code:
x := F("z")
MsgBox %x%

F(_)
{
   r = /%_%\
   Return %r%t
}

I get:
Code:
---------------------------
test.ahk
---------------------------
Error: This variable or function name contains an illegal character.  The current thread will exit.

Specifically: /z\t

   Line#
   012: x := F("z")
   013: MsgBox,%x%
   016: {
   017: r = /%_%\
--->   018: Return,%r%t
   019: }
   020: Exit
   020: Exit

---------------------------
OK   
---------------------------

Good message, but the function name is absent!
Note also the duplicated Exit line (20)...
_________________
vPhiLho := RegExReplace("Philippe Lhoste", "^(\w{3})\w*\s+\b(\w{3})\w*$", "$1$2")
Back to top
View user's profile Send private message Visit poster's website
evl



Joined: 24 Aug 2005
Posts: 1239

PostPosted: Tue Feb 28, 2006 5:50 pm    Post subject: Reply with quote

Seems to be quite effective (the 1st post of course Laughing ) I managed to shorted it a little (as a learning experience for myself):

Code:

IncrementName(nameNoExt)
{
   Loop
    {
    StringRight d, nameNoExt, 1
    StringTrimRight nameNoExt, nameNoExt, 1
    If (d = 9)
      {
      dd = 0%dd%
      Continue
      }
    If d between 0 and 8
      dd := (d + 1) . dd 
    Else
      nameNoExt = %nameNoExt%%d%1 ; Not a digit - put back non digit char and add a 1
    Break
    }
  Return nameNoExt . dd
}
Back to top
View user's profile Send private message
PhiLho



Joined: 27 Dec 2005
Posts: 6721
Location: France (near Paris)

PostPosted: Tue Feb 28, 2006 6:14 pm    Post subject: Reply with quote

Yes, for some reason (trials and errors...), I made some redundant code.
I integrated some of your suggestions, but I prefer my flow of control. To each his owns...

While I am here, I can as well put the code using this function. It is part of my permanent script (always on):
Code:
;----------------------------------------------------------------
; Explorer hotkeys
; The Explorer class below may change depending on system
; and the way the window is created. Works for me... (Win+E in XP)
#IfWinActive ahk_class ExploreWClass

;--- Shift+F9: in Explorer, create a new folder
+F9::
   appName = Create Folder
   ; Get current path in Explorer
   ControlGetText currentPath, Edit1, ahk_class ExploreWClass
   InputBox folderName, %appName%, Input new folder name
   If ErrorLevel <> 0   ; Canceled
      Return
   IfNotExist %currentPath%\%folderName%
   {
      FileCreateDir %currentPath%\%folderName%
   }
   else
   {
      MsgBox 16, %appName%, A folder with this name already exists!
      Send {F5}
   }
Return

;----------------------------------------------------------------
;--- F9: in Explorer, duplicate current file
F9::
   appName = Duplicate File
   ; Get current path in Explorer
   ControlGetText currentPath, Edit1, ahk_class ExploreWClass
   ;--- Get current (selected/with focus) filename in Windows Explorer
   selectedFile := GetExplorerSelectedFile()
   If (selectedFile = "")
   {
      MsgBox 16, %appName%, No selected file!
      Return
   }
   InputBox wantedName, %appName%
         , Duplicate of '%selectedFile%'`nInput file suffix`n(* to increment number)`n(or full name if prefixed with /)
   If ErrorLevel <> 0   ; Canceled
      Return

   SplitPath selectedFile, fileName, , ext, nameNoExt

   IfInString wantedName, /
   {
      StringReplace newName, wantedName, /
   }
   Else IfInString wantedName, *
   {
      newName := IncrementName(nameNoExt) "." ext
   }
   Else
   {
      fileSuffix := wantedName
   }
   If fileSuffix
   {
      newName = %nameNoExt%%fileSuffix%.%ext%
   }
   IfNotExist %currentPath%\%newName%
   {
      ; May fail if fileSuffix contains a forbidden char... I suppose user is smart...
      If InStr(FileExist(currentPath "\" selectedFile), "D")
      {
         FileCreateDir %currentPath%\%newName%
      }
      Else
      {
         FileCopy %currentPath%\%selectedFile%, %currentPath%\%newName%
      }
   }
   else
   {
      MsgBox 16, %appName%, A file named '%newName%' already exists!
   }
;   Send {F5}
Return

#IfWinActive

; (Some other hotkeys...)


GetExplorerSelectedFile()
{
   local selectedFiles, file

   ControlGet, selectedFiles, List, Selected Col1, SysListView321, ahk_class ExploreWClass
   Loop, Parse, selectedFiles, `n  ; Rows are delimited by linefeeds (`n).
   {
      If (A_Index = 1)
      {
         file := A_LoopField
      }
      Else
      {
         ; Indicate that several files are selected, we return only the first one
         ErrorLevel := A_Index
      }
   }
   Return file
}

_________________
vPhiLho := RegExReplace("Philippe Lhoste", "^(\w{3})\w*\s+\b(\w{3})\w*$", "$1$2")


Last edited by PhiLho on Fri Apr 07, 2006 2:59 pm; edited 2 times in total
Back to top
View user's profile Send private message Visit poster's website
Titan



Joined: 11 Aug 2004
Posts: 5374
Location: /b/

PostPosted: Tue Feb 28, 2006 7:15 pm    Post subject: Reply with quote

evl wrote:
Seems to be quite effective (the 1st post of course Laughing ) I managed to shorted it a little (as a learning experience for myself):
I've made further size reductions (note naming difference):
Code:
iN(n) {
   Loop {
      StringRight, d, n, 1
      StringTrimRight, n, n, 1
      If d = 9
         dd = 0%dd%
      Else {
         If (d >= 0 and d <= 8)
            dd := (d + 1) . dd
         Else
            n = %n%%d%1
         Break
      } }
   Return, n . dd
}

_________________

Back to top
View user's profile Send private message Visit poster's website
Chris
Site Admin


Joined: 02 Mar 2004
Posts: 10471

PostPosted: Tue Feb 28, 2006 10:51 pm    Post subject: Reply with quote

PhiLho wrote:
First error: I forgot to put a global or local d, dd declaration on top of the function.
There is a tentative plan to support "super globals", which might work by using the global keyword outside of a function. Such a global would then become visible inside all functions without having to declare it in every one.

Quote:
The current thread will exit.
---> 018: Return,%r%t
Good message, but the function name is absent!
The function name is absent because this error is referring to the fact that it thinks you're trying to build a dynamic variable name that contains bad characters (in the "return" line). This error is not specific to functions.

Quote:
Note also the duplicated Exit line (20)...
That is normal. It simplifies the script in memory in a way that allows runtime performance to be increased.

Thanks.
Back to top
View user's profile Send private message Send e-mail
PhiLho



Joined: 27 Dec 2005
Posts: 6721
Location: France (near Paris)

PostPosted: Tue Feb 28, 2006 11:33 pm    Post subject: Reply with quote

Chris wrote:
Quote:
The current thread will exit.
---> 018: Return,%r%t
Good message, but the function name is absent!
The function name is absent because this error is referring to the fact that it thinks you're trying to build a dynamic variable name that contains bad characters (in the "return" line). This error is not specific to functions.

What I meant is that the whole (small) script is listed, except comments at start and empty lines, of course, and except line 15, where there should be the function definition (name & params).
It is OK (probable technical reason), but look strange when you wonder if the function is really executed, or even seen...
I knew it was a bug on my side, or at least I tried hard to find a proof, to avoid making a bug report, putting the blame on AHK instead of on my own code... Smile

@Titan: I am not trying to make the shortest code, but one readable and easy to follow. Which doesn't mean your version isn't good... Smile
But that's why I put my braces on separate lines, and even around a single line.
I like the If d between 0 and 8 construct...
Oh, I will put the 9 case first, as it is apart of the two others breaking the loop. And for consistency, I drop the expression for this If.
_________________
vPhiLho := RegExReplace("Philippe Lhoste", "^(\w{3})\w*\s+\b(\w{3})\w*$", "$1$2")
Back to top
View user's profile Send private message Visit poster's website
Chris
Site Admin


Joined: 02 Mar 2004
Posts: 10471

PostPosted: Wed Mar 01, 2006 12:54 am    Post subject: Reply with quote

PhiLho wrote:
...the whole (small) script is listed... except line 15, where there should be the function definition (name & params).
Ah, thanks for clarifying. Yes, function definitions aren't shown in ListLines because they're not an executable part of the script. It would be nice to see them, so maybe this can be added someday.
Back to top
View user's profile Send private message Send e-mail
toralf



Joined: 31 Jan 2005
Posts: 3841
Location: Bremen, Germany

PostPosted: Wed Mar 01, 2006 8:15 am    Post subject: Reply with quote

Hi Philho,

Thanks for sharing it. Specially the history to track down the bugs might help other people.

:) Again: Some minds work the same; I coded a similar function .
_________________
Ciao
toralf
Back to top
View user's profile Send private message Send e-mail Visit poster's website
PhiLho



Joined: 27 Dec 2005
Posts: 6721
Location: France (near Paris)

PostPosted: Wed Mar 01, 2006 10:02 am    Post subject: Reply with quote

Yes, I thought I wasn't the first to make such function, but I thought it would be faster to rewrite it than to search for it (I was wrong Razz) and I had fun making it.

Thanks for reminding your function, it does much more than mine.
As you saw, I made the test for file existence outside the function and just fail if it exists. I could iterate, but I don't really need that.
_________________
vPhiLho := RegExReplace("Philippe Lhoste", "^(\w{3})\w*\s+\b(\w{3})\w*$", "$1$2")
Back to top
View user's profile Send private message Visit poster's website
Laszlo



Joined: 14 Feb 2005
Posts: 4063
Location: Pittsburgh

PostPosted: Thu Mar 02, 2006 4:07 am    Post subject: Reply with quote

Toralf was not the first, either. There was a thread a while ago, where someone asked to generate guaranteed unique filenames. The problem with the counter approach is that at the second run you have to find the first unused filename. (Of course, if other applications write in the same directory, there could be conflicts later, too.) We concluded at that time that the simplest solution (5 lines of code) is to name the files by the current date+time (A_Now). At an unexpected conflict, just keep on trying. The time automatically increases, and eventually you find an unused filename. However, this only happens if unknown applications also write into your working directory, which is not common.
Back to top
View user's profile Send private message
PhiLho



Joined: 27 Dec 2005
Posts: 6721
Location: France (near Paris)

PostPosted: Thu Mar 02, 2006 10:29 am    Post subject: Reply with quote

Good strategy. Note that my function has been created essentially to allow me to quickly duplicate a file manually in Explorer. I used to do Ctrl+drag'n'drop of file + rename (remove Copy of..., etc.), this is slightly faster.

Note that Chris could provide an access to the Ansi-C function tmpnam which is supposed to provide a unique filename without clash risk.

Well, it is a bit controversed, as you can get this filename unique at creation time, and use it only later, and some external program could create this filename in the mean time... But frankly, risks of such clash is very low, unless you run a server creating thousand of files per second...
_________________
vPhiLho := RegExReplace("Philippe Lhoste", "^(\w{3})\w*\s+\b(\w{3})\w*$", "$1$2")
Back to top
View user's profile Send private message Visit poster's website
Chris
Site Admin


Joined: 02 Mar 2004
Posts: 10471

PostPosted: Thu Mar 02, 2006 4:41 pm    Post subject: Reply with quote

I seem to remember there's a WinAPI function that also generates unique filenames. If so, DllCall would hopefully be able to call it.
Back to top
View user's profile Send private message Send e-mail
Display posts from previous:   
Post new topic   Reply to topic    AutoHotkey Community Forum Index -> Scripts & Functions All times are GMT
Page 1 of 1

 
Jump to:  
You can post new topics in this forum
You can reply to topics in this forum


Powered by phpBB © 2001, 2005 phpBB Group