 |
AutoHotkey Community Let's help each other out
|
| View previous topic :: View next topic |
| Author |
Message |
PhiLho
Joined: 27 Dec 2005 Posts: 6721 Location: France (near Paris)
|
Posted: Tue Feb 28, 2006 5:04 pm Post subject: Function: IncrementName (create next file) |
|
|
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 |
|
 |
PhiLho
Joined: 27 Dec 2005 Posts: 6721 Location: France (near Paris)
|
Posted: Tue Feb 28, 2006 5:29 pm Post subject: |
|
|
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 |
|
 |
evl
Joined: 24 Aug 2005 Posts: 1239
|
Posted: Tue Feb 28, 2006 5:50 pm Post subject: |
|
|
Seems to be quite effective (the 1st post of course ) 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 |
|
 |
PhiLho
Joined: 27 Dec 2005 Posts: 6721 Location: France (near Paris)
|
Posted: Tue Feb 28, 2006 6:14 pm Post subject: |
|
|
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 |
|
 |
Titan
Joined: 11 Aug 2004 Posts: 5374 Location: /b/
|
Posted: Tue Feb 28, 2006 7:15 pm Post subject: |
|
|
| evl wrote: | Seems to be quite effective (the 1st post of course ) 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 |
|
 |
Chris Site Admin
Joined: 02 Mar 2004 Posts: 10471
|
Posted: Tue Feb 28, 2006 10:51 pm Post subject: |
|
|
| 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 |
|
 |
PhiLho
Joined: 27 Dec 2005 Posts: 6721 Location: France (near Paris)
|
Posted: Tue Feb 28, 2006 11:33 pm Post subject: |
|
|
| 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...
@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...
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 |
|
 |
Chris Site Admin
Joined: 02 Mar 2004 Posts: 10471
|
Posted: Wed Mar 01, 2006 12:54 am Post subject: |
|
|
| 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 |
|
 |
toralf
Joined: 31 Jan 2005 Posts: 3841 Location: Bremen, Germany
|
Posted: Wed Mar 01, 2006 8:15 am Post subject: |
|
|
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 |
|
 |
PhiLho
Joined: 27 Dec 2005 Posts: 6721 Location: France (near Paris)
|
Posted: Wed Mar 01, 2006 10:02 am Post subject: |
|
|
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 ) 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 |
|
 |
Laszlo
Joined: 14 Feb 2005 Posts: 4063 Location: Pittsburgh
|
Posted: Thu Mar 02, 2006 4:07 am Post subject: |
|
|
| 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 |
|
 |
PhiLho
Joined: 27 Dec 2005 Posts: 6721 Location: France (near Paris)
|
Posted: Thu Mar 02, 2006 10:29 am Post subject: |
|
|
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 |
|
 |
Chris Site Admin
Joined: 02 Mar 2004 Posts: 10471
|
Posted: Thu Mar 02, 2006 4:41 pm Post subject: |
|
|
| 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 |
|
 |
|
|
You can post new topics in this forum You can reply to topics in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|